material-plus-ui
v1.14.0
Published
A React component library written from the Material Design 3 specification and extended past it — wider versions of the components it names, and the charts, data table and command palette it does not. Built on Base UI and Tailwind CSS v4, themed with CSS
Maintainers
Keywords
Readme
Material Plus for React
This README is the quick start. The documentation site has a page per component, with live previews and the full props table. The same components ship for Flutter as material_plus_ui, under the same names and the same prop vocabulary; the two packages version independently.

Material Plus is a React component library implementing Material Design 3. It follows the specification directly rather than wrapping somebody's implementation of it, so its colour roles, type scale and shapes are the ones the spec names.
It also goes on past where the specification stops. The button ladder keeps the spec's 32, 40 and 56px and adds 64 and 72 — the next size the spec names is 96, and a 96px button beside a 64px field is not a row — and the charts, the data table and the command palette are components the spec never named at all.
- Themed in one line. Set
--mp-source-colorand every colour role follows, the way Material generates a scheme from a source colour. No provider, no theme object, no re-render. - It coexists. Nothing here is page-level — no reset, no provider, no global styling — and it reads the
--md-sys-color-*tokens your page already has if they are there. A project already running Material keeps its own setup. - IME-safe by construction. Korean, Japanese and Chinese composition survives whatever your
onChangedoes with the value. - Bring your own icons.
MPIcontakes a component or an element from any set.lucide-reactships with the package, gathered in one readable constants file. - Works in a Next.js server component, and in any bundler: the components carry
"use client", the barrel and the data do not, and every specifier indist/names a file — so webpack, Vite, esbuild, Rollup and plain Node all resolve it. - ESM only, TypeScript declarations included, and tree-shakeable for real: one component is 3.8 kB gzipped and five are 10.1 kB, against 139.5 kB for every export there is.
npm run measureprints those numbers off a real bundler, so they cannot quietly stop being true. The stylesheet splits the same way. - Two runtime dependencies, and the second is loaded rather than bundled:
lucide-reactfor the glyphs, andhighlight.jsbehindMPCodeBlock— every grammar sits behind animport(), so a page that renders no code block never downloads one. React 18 or 19, Node.js 22 or later.
Documentation
Everything is documented at material-plus.cdget.com, where the previews are not screenshots — they are the components, running in the page.
| Page | What you will find | | --- | --- | | Getting started | Install and setup, end to end. | | All components | Every component, one page each: live previews and the full props table. | | A2UI catalog | Rendering an agent's interface: A2UI's basic catalog, drawn as Material Design 3. | | Browser support | Which browsers and versions it runs in, and what is reduced in the older ones. | | Changelog | What changed in each release. |
Also available in Korean / 한국어 문서: material-plus.cdget.com/ko/
Installation
npm install material-plus-uipnpm add material-plus-uiPeer dependencies
| Package | Versions | Required |
| ------------------------------- | -------- | -------------------------------- |
| @base-ui/react | 1 | Yes |
| react, react-dom | 18 or 19 | Yes |
| @a2ui/react, @a2ui/web_core | 0.15 | Only for material-plus-ui/a2ui |
@base-ui/react is a peer rather than a dependency because it carries React context: a Form of yours has to be able to see a field of ours, and that only works with one copy in the tree.
The two @a2ui packages are optional and only needed to render an agent's interface — see the A2UI catalog. Install nothing extra and the rest of the library is unaffected.
Setup
Add one line to your app's CSS entry point:
@import 'material-plus-ui/styles.css';This is finished CSS — no PostCSS plugin, no @source, no build-side configuration. It deliberately contains no reset, and no page-level styling of any kind: a component resets what it owns on the element it owns, so whatever your page already does stays in charge.
If Tailwind v4 is already in your project, import the token sheet instead:
@import 'tailwindcss';
@import 'material-plus-ui/tailwind.css';A page that renders a handful of components can take the tokens and a sheet each instead of the whole 175.9 kB — 6.4 kB gzipped for one component against 23.9 kB for the lot. The guide has the numbers and where the two paths cross.
import 'material-plus-ui/styles/tokens.css';
import 'material-plus-ui/styles/button.css';Languages other than English
The library carries English. Every other translation is a module you hand over, once, at startup:
import { registerMPMessages } from 'material-plus-ui';
import { ko } from 'material-plus-ui/locales';
registerMPMessages(ko);After that locale="ko" resolves the way it always did. Eighteen languages ship; registerMPMessages(...LOCALES) takes all of them, a tag nobody registered falls back to English, and a table of your own is a plain object. See the localisation guide.
Usage
There is no provider. Import a component and render it.
import { useState } from 'react';
import { MPTextField, MPIcon, ICONS } from 'material-plus-ui';
export default function App() {
const [email, setEmail] = useState('');
return (
<MPTextField
label="Email"
type="email"
value={email}
onChange={setEmail}
startIcon={<MPIcon icon={ICONS.search} size={18} />}
/>
);
}Theming
Material generates a whole scheme from one source colour, and so does this.
:root {
--mp-source-color: #7c3aed;
}Dark mode follows prefers-color-scheme on its own; data-mp-scheme="dark" or .dark on any element drives it yourself. To disagree with one generated colour, set that role — --mp-sys-color-outline — and to follow tokens your page already defines, do nothing: --md-sys-color-* is read where it is present.
Full details, including scoped and runtime theming, are in the theming guide.
Components
There are over 120, grouped into layout, inputs, display, feedback, motion and charts. Each has a page of its own at material-plus.cdget.com/components/, with the full props table and previews that are the component running in the page.
Why MPTextField exists
A controlled <input> renders from its value. While an input method is composing, the browser holds a provisional string in the element that has not been committed — and writing value back over it in that moment throws away the syllable in progress and jumps the caret. Trimming, upper-casing or validating in your onChange is enough to trigger it.
MPTextField stops rendering value for the duration of a composition and shows what the element actually contains, while onChange keeps firing for every keystroke. When the composition ends, the field is controlled again.
// Upper-cases everything — and a Korean syllable still composes correctly.
<MPTextField value={name} onChange={(next) => setName(next.toUpperCase())} />Contributing
Issues and pull requests are welcome. CONTRIBUTING.md has the repository layout, the commands and what a component is expected to own. Participation is subject to the Code of Conduct.
This package lives in packages/react of the repository, and is entered and run on its own:
cd packages/react
npm install # also builds, via `prepare`
npm run test # vitest, in a real browser
npm run typecheck # src and test
npm run lintThe documentation site is a package of its own in docs/: cd docs && npm install && npm run dev.
To report a security issue, follow the process in SECURITY.md instead of opening a public issue.
For anything else that does not belong in a public issue, write to CDGet at cdget.com/contact.
Sponsor
Material Plus is free to use and maintained in the open. If it saves you time, you can support the work at cdget.com/donate or through the Sponsor button on GitHub.
