# mui-rff: thin wrapper components that connect Material UI inputs to React Final Form

> mui-rff is an MIT-licensed TypeScript package of wrapper components that wire MUI inputs into React Final Form, covering state, validation and error display, with peer dependencies pinned to React 19, MUI 9 and Final Form 5. It suits a React team that picked both libraries and got tired of the glue code, and it depends on a large compatibility surface.

**lookfirst/mui-rff** — MUI / Material UI + React Final Form

- Repository: https://github.com/lookfirst/mui-rff
- Website: https://lookfirst.github.io/mui-rff/
- Stars: 541 · Forks: 92
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/lookfirst-mui-rff

## The problem it solves, and the size of the wrapper

mui-rff provides thin, tested wrapper components that connect MUI, Material UI, with React Final Form. It removes the repetitive glue code needed to wire form state, validation and error display into common MUI inputs, so you can focus on building forms instead of bridging two libraries.

That description is precise, and the word thin is doing real work. A wrapper that reimplements an input is a maintenance liability, because every behavioural change in the underlying component has to be re-implemented or forwarded. A wrapper that forwards props and adds a field binding keeps the surface small, and the package's own component list shows the shape of that: Autocomplete, Checkboxes, DatePicker, DateTimePicker, Radios, Select, Switches, TextField, TimePicker, and a component called Debug.

The Debug component is the tell that this is a thin layer rather than a reimplementation. It exists to inspect the form state, which means the wrappers do not own the state, they read it, and the state belongs to Final Form underneath.

The project states that it is 7+ years old and still actively supported, and that a large suite of unit tests makes that practical by catching compatibility issues early as React, MUI, Final Form and related dependencies evolve. It also has an automated CI and release pipeline, so dependency updates and releases are validated consistently instead of relying on manual steps.

That last point is the honest reason to use a wrapper like this rather than writing the glue yourself. The glue is easy to write once and expensive to maintain across a major version of any of the four libraries involved. A package whose test suite runs against the current releases turns that maintenance into a recurring automated task, and the alternative to it is somebody's Thursday afternoon.

The licence is MIT, the primary language is TypeScript, the homepage is lookfirst.github.io/mui-rff, and the recent releases are v10.0.1 from 2026-04-20, v10.0.0 from 2026-04-15 and v8.2.25 from 2026-04-14, with the last push to the repository on 2026-06-16.

## Installing it, and the optional pieces that catch people out

The install is one command with the peer dependencies listed explicitly:

```bash
bun add mui-rff @emotion/react @emotion/styled @mui/material @mui/system @mui/x-date-pickers final-form react react-final-form
```

The peer dependencies are the whole list, and the package.json is described as the authoritative source for the peer dependency ranges. The current release targets React 19, react-final-form 7, final-form 5, @mui/material 9 and @mui/x-date-pickers 9, with the exact ranges pinned in the manifest rather than implied.

Two optional groups are easy to miss, because the package works without them and fails at runtime rather than at install time if you assumed they were there.

If you use the date pickers you also need a date adapter:

```bash
bun add @date-io/core @date-io/date-fns date-fns
```

And if you use the Yup-based validation helpers you need yup:

```bash
bun add yup
```

Both are in the optional dependencies, with the date packages at 3.2 and later and yup at 1.7 and later. The date adapter is the one that bites hardest, because the date pickers are MUI's own components and they need to be told which date library to use, and the wrapper delegates that to MUI's LocalizationProvider rather than configuring it itself.

The consequence is that a consumer of this package has three dependency decisions rather than one, and each of them is a version that has to move with MUI's own release train. That is the price of the convenience, and it is a price the manifest makes visible rather than hiding.

## What a form looks like with the wrappers

The usage model is that you build a Form with React Final Form and then render mui-rff fields inside it. The basic example is short:

```tsx
import { Form } from 'react-final-form';
import { TextField } from 'mui-rff';

export function MyForm() {
	return (
		<Form<FormData>
			initialValues={{ hello: 'hello world' }}
			onSubmit={onSubmit}
			validate={validate}
			render={({ handleSubmit, values }) => (
				<form onSubmit={handleSubmit} noValidate>
					<TextField label="Hello world" name="hello" required />
					<pre>{JSON.stringify(values, null, 2)}</pre>
				</form>
			)}
		/>
	);
}
```

Two things are visible in that snippet. The form is a Final Form form, with its render prop, its initial values and its validate function, and the mui-rff component appears inside it as an ordinary MUI input with a name. And the noValidate attribute is on the form element, which is the standard Final Form arrangement, because the library handles validation display itself rather than relying on the browser's native messages.

The individual components follow the same pattern, and the examples are deliberately small so that the difference from a plain MUI input is visible. A Select takes children, which are MUI MenuItems passed straight through, and a Checkboxes or a Radios take a data array of label and value pairs, and a Switches takes a single data object, and an Autocomplete takes options with getOptionLabel and getOptionValue, plus the usual MUI props such as disableCloseOnSelect and multiple, and a renderOption that can add a checkbox.

That renderOption example is worth noting because it is the kind of thing the wrapper has to let you do. A multiple Autocomplete with a custom option renderer is the case where a wrapper that restricted the MUI surface would be useless, and the example shows the props flowing through to a custom li element with the sx prop on the checkbox.

There is one behavioural detail in the documentation that is easy to get wrong, and it is about the multiple Autocomplete: when multiple is enabled, the corresponding initialValues entry should be an array, so the form state for that field is an array rather than a scalar.

## The error and validation helpers, and how the wrappers handle errors

Beyond the components, the package exports a set of validation and error utilities: makeRequired, makeValidate, makeValidateSync, ErrorMessage, showErrorOnBlur, showErrorOnChange, and useFieldForErrors.

The names describe a small opinionated layer over Final Form's own validation model. makeRequired produces a validator that fails when a field is empty, and makeValidate and makeValidateSync build validators, synchronously or otherwise, from a schema, and the documentation says those helpers are available if you use Yup schemas, which is why yup is an optional dependency. ErrorMessage is the component that renders an error, and showErrorOnBlur and showErrorOnChange are the switches that decide when an error becomes visible, which is a detail that matters more than it sounds, because a form that shows an error while the user is still typing is a form people stop using.

useFieldForErrors is the hook that ties the two together, and it is the piece that keeps the wrappers thin. The component reads the field state and decides whether the error should be shown according to the configured mode, and it renders the MUI error state and the helper text from the same source of truth.

The set of names is a reasonable design. The alternative, which a lot of applications end up with, is a per-form convention: some forms validate on blur, some on change, some on submit, and each input decides for itself, and the inconsistency is a support burden. Pushing that decision into one place and making it configurable is the kind of small opinion that a wrapper library should have.

The customization story is the pass-through props. Each wrapper accepts the usual MUI props plus component-specific pass-through props for nested elements, and the examples show a TextField with fieldProps carrying a validate function, a Select with menuItemProps carrying disableGutters, and a DatePicker with TextFieldProps carrying variant set to outlined.

## The build, the tests, and the repository layout

The repository is a modern Vite and Vitest project with a Biome-based lint setup, and the scripts show the whole pipeline in one place.

```json
  "scripts": {
    "start": "vite build --watch",
    "build": "vite build",
    "test": "vitest run",
    "lint": "biome lint",
    "tsc": "tsc --noEmit",
    "ci": "bun run lint && bun run tsc && bun run test && CI=true bun run build && bun run build-example",
    "gh-pages": "gh-pages -d example/dist"
  }
```

The ci script is the interesting one. It runs the linter, the type check, the tests, a production build and a build of the example application, in that order, and the CI=true environment variable on the build step is the usual marker for the reproducible, non-watch production build. The example application is a separate workspace, listed in the workspaces field, with its own package.json, index.html, vite.config.ts and tsconfig, and the gh-pages script publishes that example build, which is what produces the live demo site.

The test script is vitest run with a watch variant alongside it, and the test directory is linked from the documentation next to the example source, so both are first-class parts of the repository rather than an afterthought.

Two other details are worth noting. The package publishes both a CommonJS and an ES module build, with main pointing at dist/mui-rff.cjs.js, module at dist/mui-rff.esm.js and typings at dist/index.d.ts, and the files field ships dist and src, so the TypeScript sources travel with the package. And sideEffects is set to false, which tells a bundler that importing the package for its types alone has no runtime cost, a small thing that keeps a types-only import from pulling the whole library into a bundle.

The repository also has a CI.md documenting the pipeline, a biome.jsonc configuration, a bun.lock, a CODE_OF_CONDUCT.md and a CONTRIBUTING.md, and the tooling is uniform: Biome for linting, an ultracite check script as a second opinion, and Vitest with Testing Library for the tests.

## Where mui-rff is the wrong tool

The first limit is that it only helps if you use both libraries. A team that has chosen Material UI with React Hook Form gets nothing from this package, and a team on Formik or on plain React state gets nothing either. The value is entirely in the overlap.

The second is the coupling, which is the cost of a wrapper and the reason the package description insists the wrappers are thin. A wrapper library is only as stable as the least stable of the components it bridges, and here there are four: React, MUI, Final Form and React Final Form. The version ranges are pinned closely, which is correct, and it also means an MUI major upgrade is a mui-rff upgrade, and the documentation makes the current targets explicit rather than leaving you to discover them at install time.

The third is the peer dependency surface. The install command lists seven packages, and the optional date adapter and Yup add two more, and every one of them is a version that has to be tracked. A team with a strict dependency policy, or a monorepo that pins versions centrally, will find that adding this package is a negotiation rather than a one-line change.

The fourth is the coverage of the component set. The exported list is ten components and it is not the whole of MUI, so a form that uses a Slider, a Transfer, a file input or a Rating has no wrapper and the team is back to writing the glue by hand for those fields, with a differently shaped API alongside the wrapped ones. That is the natural consequence of a thin wrapper and it is worth knowing before you commit to it for a large form.

And the fifth is the alternative worth naming. React Final Form has its own render component approach, and a team that wrote one render component per field type would have exactly the same coupling with none of the dependency surface, at the cost of maintaining it. The trade is time now against time later, and a wrapper wins that trade when the form library and the component library are both settled. A team that is still deciding between them, or that expects to change one of them, is better served by writing the small amount of glue itself.

## Conclusion

mui-rff is worth adding if your application already uses both Material UI and React Final Form, because the alternative is writing the same state, validation and error plumbing for every input, and because the wrappers are thin enough that they do not hide the underlying libraries, so a component that is not covered can still be written by hand against the same field API. It is the wrong tool if you use only one of the two libraries, and it is the wrong tool if you want the two ecosystems to stay independent. Before you install it, check three things: that your versions are in range, since the current release targets React 19, react-final-form 7, final-form 5, @mui/material 9 and @mui/x-date-pickers 9, and the package.json is the authoritative source for the peer dependency ranges; that you install the optional pieces you actually use, since the date pickers need a date adapter such as @date-io/core with @date-io/date-fns and date-fns, and the Yup-based validation helpers need yup, and those are optional dependencies rather than included ones; and that you accept the coupling, because a wrapper library is only as stable as the least stable of the four libraries it bridges, and this one exists precisely because those libraries do not agree on how a field should look.

## FAQ

### How do I install mui-rff with React Final Form and Material UI?

Run bun add mui-rff @emotion/react @emotion/styled @mui/material @mui/system @mui/x-date-pickers final-form react react-final-form. The current release targets React 19, react-final-form 7, final-form 5, @mui/material 9 and @mui/x-date-pickers 9, and the package.json holds the authoritative peer dependency ranges.

### Do I need anything extra to use the date pickers or the Yup validation helpers?

Yes. The date pickers need a date adapter, installed with bun add @date-io/core @date-io/date-fns date-fns, and they must be wrapped in MUI's LocalizationProvider with that adapter. The Yup-based validation helpers need yup, installed with bun add yup. Both are optional dependencies rather than included ones, so a missing one shows up at runtime rather than at install time.

### Which components and helpers does mui-rff export?

The component wrappers are Autocomplete, Checkboxes, DatePicker, DateTimePicker, Radios, Select, Switches, TextField, TimePicker and Debug. The validation and error utilities are makeRequired, makeValidate, makeValidateSync, ErrorMessage, showErrorOnBlur, showErrorOnChange and useFieldForErrors, and each wrapper also accepts the usual MUI props plus pass-through props such as fieldProps, menuItemProps and TextFieldProps.

## Sources

- [License: MIT](https://github.com/lookfirst/mui-rff/blob/master/LICENSE)
- [lookfirst/mui-rff on GitHub](https://github.com/lookfirst/mui-rff)
- [Project website](https://lookfirst.github.io/mui-rff/)
- [README](https://github.com/lookfirst/mui-rff/blob/master/README.md)
- [Releases](https://github.com/lookfirst/mui-rff/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lookfirst-mui-rff
