Skip to content

Releases: floor/material

material 3.2.4

Choose a tag to compare

@github-actions github-actions released this 20 Oct 17:34
ff83519

Keyboard focus stays inside the surface you open and returns to the field you
chose from.

Fixes

  • An externally mounted <m-dialog> restores its surface before opening
    (#116).

    Following the createDialog example — appending dialog.element to
    document.body, then calling dialog.open() — moved the surface out of
    its overlay; opening then made every overlay sibling inert, including the
    dialog itself. It was visible, but initial focus stayed on BODY and Tab
    reached no action, while the unchanged default mount worked in the same
    browser run. Before an overlay dialog opens, its surface now returns to its
    own overlay when the caller mounted or detached it elsewhere; the move
    happens after beforeopen has accepted the request and leaves the native
    top-layer path unchanged. Measured in Chromium across six mount paths —
    unchanged, body-appended, nested, configured container, detached and native
    top layer — each opens with initial focus on the Cancel button, Tab and
    Shift+Tab wrap inside the dialog, Escape closes it with focus returned, the
    background stops scrolling and restores it, an existing inert attribute
    is preserved, and a remount reopens cleanly.

  • Enter on a focused day in <m-datepicker> saves the date and closes the
    calendar (#117).

    With a day focused, Enter fell through the key handler and activated the
    day button as a native click. Default docked pickers committed the date but
    stayed open; modal variants kept it as a draft and stayed open, and focus
    stayed on the day instead of returning to the field. Enter is now handled
    explicitly for single-date selection, through the existing commit and close
    path, with the native click suppressed so one key press emits one change
    and one close. It accepts independently of closeOnSelect, while pointer
    and Space selection keep that option; range endpoints still select without
    prematurely accepting, and modal ranges keep their confirmation action.
    Measured in Chromium across 16 single-date cases — docked, modal,
    modal-input calendar mode and full-screen, in document and shadow-root
    placement, under both closeOnSelect settings — each saves once, closes
    the API and native dialog, clears aria-expanded, and returns real focus
    to the field; four range controls preserve endpoint selection and
    confirmation.

Internal

  • The two fixes cost 25 bytes gzipped in the all-js row (127,427 to
    127,452) and nothing in the dialog's own: 13,496 bytes gzipped against its
    unchanged 13,619-byte ceiling.
    No ceiling moved.

npm: material@3.2.4 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.2.3

Choose a tag to compare

@github-actions github-actions released this 20 Oct 14:38
cc59b41

Elements hold their baseline at upgrade.

Fixes

  • <m-tabs> keeps its reserved baseline at upgrade (#108).
    In a baseline-aligned row beside a switch and a text field, upgrading
    <m-tabs> moved it down 12.90625px and moved its neighbors up 6.5px: the
    empty pre-upgrade block synthesized a baseline at its bottom edge while the
    upgraded block exported a different baseline from its first rendered tab,
    though both boxes were already 48px tall. The tabs host now carries
    contain: layout, so it keeps synthesizing its baseline from its own box;
    paint overflow remains visible. Measured over 40 strict browser cases
    (LTR/RTL, the default page typography and four overrides, text and icon
    tabs, tabs-only and shared upgrades): the host shifts 0.00px and every
    neighbor moves 0.00px, with the 48px and 64px heights held.

  • <m-radios> reserves the upgraded baseline before upgrade (#109).
    Beside a switch and a text field in a baseline-aligned row, <m-radios>
    moved up 5px when the elements were defined: pre-upgrade the host exposed
    the first row's label-text baseline, 29px down its box, and after upgrade
    the first row's control baseline, 34px down. The undefined host is now the
    column it becomes after upgrade, and its first item is a zero-width strut
    ending on the circle's bottom edge (height: 34px; margin-block-end: -34px),
    so the row holds the host in place with no horizontal change. The strut
    follows an actual option — :has(> m-radio) — so an empty group, compact or
    indented, reserves nothing and its neighbors do not move. Measured at the
    default page typography and 12px/1, 12px/2, 24px/1 and 24px/2: the host
    moves 0.00px and the row keeps its 144px height.

Internal

  • The upgrade holds cost a few bytes and no ceiling moved. Layout
    containment on the tabs host leaves the tab row at 13,642 bytes gzipped
    against its unchanged ceiling; the radios guard adds 42 bytes across the
    three pre-upgrade assets and 520 bytes unpacked with its packed source.

npm: material@3.2.3 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.2.2

Choose a tag to compare

@github-actions github-actions released this 20 Oct 12:05
0afb58e

Components follow the page's direction, inside shadow roots and in bundled
output.

Fixes

  • Direction rules match inside a shadow root, at the nearest [dir], and
    follow live changes (#113).

    The rtl mixin compiled to [dir='rtl'] &, and two sheets wrote that
    selector out literally; a descendant selector cannot cross a shadow
    boundary, so the page's <html dir="rtl"> outside the element's shadow
    tree never reached its rules, though every host inherited direction: rtl
    and matched :dir(rtl). A shared direction-resolution module now sets
    mtrl-rtl / mtrl-ltr on the roots it watches, from the element's computed
    style and, for a factory root, the nearest [dir]; the direction mixin
    emits the class arms, a :host(...) arm and an attribute fallback, and a
    document-level MutationObserver follows dir changes. Measured at 600px:
    the drawer's start sheet moves 0→240, its item padding 16/24→24/16, the
    dialog's close button x 528→32, and a subtree's nearer [dir] wins over the
    page both ways.

  • <m-slider> mirrors its value, segments and stops on a right-to-left
    page (#114).

    The axis was resolved with element.closest("[dir]"), and a selector
    cannot cross a shadow boundary: the host inherits direction: rtl, but the
    page's dir lies outside the shadow root, so the lookup returned null
    and every left-to-right position stayed. The axis now reads the element's
    computed direction, which inherits into the shadow tree, and keeps the
    nearest [dir] as the fallback where direction does not resolve (JSDOM).
    Measured on a 600px page, <m-slider value="40">: the handle moves
    x 233→353, the segments swap (0/248→368/0), and the stop moves 590→6 — each
    the mirror of its LTR box.

  • A multi-browse carousel lays out and scrolls on a right-to-left page
    (#115).

    The carousel read and wrote scrollLeft in left-to-right arithmetic and
    placed every item with translate3d(startPx, 0, 0), while Chromium reports
    an RTL scroller's scrollLeft from 0 at the inline start down to negative
    values toward the left: the scroller settled at the wrong end and the items
    landed mid-container. At 600px the main build settled at scrollLeft -232
    with items at 8.91, 187.47 and 237.59. Every scrollLeft read and write is
    now normalised (rtl ? -x : x), items anchor to the track's right edge in
    RTL, the translate is negated so the clip insets mirror with it, and the
    arrow keys walk the way the page reads: the scroller starts at the
    inline-start edge and the items sit at 314.67, 40 and -118.67, each the
    mirror of its LTR box with the 2.66667px clip.

  • The toolbar and date picker mirror in a consumer's bundled output
    (#121).

    _toolbar.scss and _datepicker.scss wrote their direction rules with
    :dir(rtl), which a bundler lowers into :lang() lists: the lowered arms
    select languages, not direction, so they matched no element on a
    dir="rtl" page — the bundled sheet carried 12,768 :lang() entries, 342
    of them from these six rules. Both sheets now route the rules through the
    direction mixin, so the toolbar keeps its hidden offset off the edge it
    leaves, the date picker's range band starts at the selection's centre and
    its month chevrons point the way the months go, in unbundled and bundled
    output alike.

  • A closed side sheet leaves the viewport on the page's logical edge
    (#112).

    Placement uses logical justify-content, which reverses in right-to-left,
    but the closed sheet moved with physical translateX(-100%/100%) and its
    solid overshoot cover never reversed: the closed sheet stood inside the
    viewport at x=256 and painted a 32px stripe back into the page. Both now
    follow the logical end, so the closed sheet sits at x=-256 with its cover
    at -32.

Internal

  • Direction resolution in the shared kernel costs ~220 bytes per entry
    (175-290 measured).
  • The carousel's direction fix costs ~+44 B gzip on the carousel row: the
    at-rest direction flip now rebuilds through a direction observer.
    The two
    ceilings that crossing required are raised rule-exact in the same change:
    carousel 11,468 to 11,600, all-js 125,849 to 127,200.
  • The packed package's unpacked ceiling is raised to 6,524,000 (#121).
    The toolbar and date picker declare their left-to-right direction values on
    each component root, so a nested LTR instance keeps them across an RTL
    parent's inherited custom properties; packed with CI's Node 22 / npm 10, the
    package measures 6,459,061 unpacked. The raise follows the rule — measured
    plus 1%, rounded up to the next 1,000.

npm: material@3.2.2 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.2.1

Choose a tag to compare

@github-actions github-actions released this 19 Oct 23:41
0a56c17

No layout jump at upgrade.

Fixes

  • <m-button> keeps its box and baseline at upgrade (#42).
    The upgraded host is now the control's own flex box — display:inline-flex; vertical-align:middle — the box the pre-upgrade rule already reserves. An
    inline-block host builds its line box from the page's typography: at
    font-size:24px; line-height:2 the 48px strut stood 8px over the 40px control
    and moved the whole line below it, and its last-line-box baseline sat
    differently from the inline-flex box's first-item baseline (−7px at 24/2,
    ±1.3–1.8px at the other measured typographies, with or without a leading
    icon). Height and baseline now hold on both sides of the upgrade at every
    measured typography, and the button keeps the spec's height per size.

  • Six more upgraded hosts stop growing with the page's line height (#51).
    m-icon-button, m-button-group, m-split-button, m-datepicker (docked),
    m-checkbox and m-select hosts are their control's own flex box instead of
    an inline-block line box, so the page's line strut no longer reaches the
    control: at 24px/2 the icon button and the split button stood 48px tall over
    40px controls, the group's pre-upgrade text item laid out a 48px line, the
    docked datepicker carried a 99px line box over its 96px control, and the
    checkbox and the select grew a pixel. The icon-button host keeps its
    bottom-edge baseline through a zero-width stretch item, matching its
    pre-upgrade box; every host holds its height and baseline across the upgrade.

  • <m-button-group> reserves its sizes, density, spacing and icons before
    upgrade (#49).

    The pre-upgrade sheet now reserves every size, density, the standard and the
    connected between-space, and text and icon buttons, in LTR and RTL, at the
    five typographies the issue measures — of the 600 measured combinations, 590
    moved on main (worst 401.66px); none moves now. The packed tarball outgrew
    its ceiling: measured 1,078,362 bytes under Node 22.23.3 / npm 10.9.9, the
    ceiling moves 1,078,000 → 1,090,000 bytes by the size script's own rule
    (measured plus 1%, to the next thousand), the one budget this change
    outgrew.


npm: material@3.2.1 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.2.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 13:46
4ed2091

Components that tell you what's wrong.

Features

  • A text field with a trailing listener but an unlabeled trailing icon now warns in
    development (#100).

    The trailing icon's click only reaches an application when the icon is a button, and a button
    needs a name: registering a trailing listener — a factory or config callback, or a listener
    added straight on <m-text-field> — without setting trailingIconLabel /
    trailing-icon-label prints one warning that says to set it. The warning is shared across
    subscriptions, rebuilds and reconnects, and the element's own bridge does not count as an
    application listener. Production builds carry none of it: the diagnostic and its state are
    stripped, so it costs no bytes and no runtime in what you ship.

  • <m-split-button> dispatches open and close when its menu opens and closes (#99).
    The same names <m-menu> and <m-fab-menu> already use, with an empty detail, fired from the
    host as the trailing button's menu expands and collapses: once per transition, whether the
    close comes from choosing an item, Escape or a click outside. select with its { value }
    payload, the leading button's native click and the factory surface are unchanged, and no
    attribute or method is added.

Fixes

  • A badge given in a tab's config is attached, and it mirrors in RTL (#92).
    A badge configured on a tab was dropped before; it now wraps the tab's icon, or its label when
    there is no icon, inside the button, so fixed tab rows keep their widths. The badge mirrors to
    the correct side in right-to-left, including in <m-tabs> under an inherited direction —
    <html dir="rtl"> or a dir="rtl" container — through native :host(:dir(rtl)) rules, with
    no observer or listener, following live dir changes.

  • Baseline's :root defaults no longer override a selected theme, in any import order (#101).
    Baseline emitted its light and dark defaults on :root and .dark-theme unconditionally, and
    :root and [data-theme="name"] share specificity, so an app compiling the theme partials
    with baseline after its own themes got baseline's colours on themed pages — including
    baseline's dark palette under a dark OS preference. The defaults are now emitted at zero
    specificity (:where(:root), :where(.dark-theme)): a selected theme always wins, while the
    roles a theme does not declare still inherit from baseline, and an unthemed page is unchanged.

Internal

  • CI fails loudly when npm's next dist-tag falls behind latest (#94).
    A dist-tags job runs after every publish and re-reads the registry, failing the run — never
    the publish, and never skipping the GitHub Release — when material@next would install an
    older build than material@latest. The same check runs locally with
    bun run check:dist-tags (or against a saved state with --tags <file>). CI only; nothing in
    the package changed.

npm: material@3.2.0 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 17:09
4008427

Added

  • Sass consumers can choose which contrast rules to compile with $contrast-emit (#95).
    Configure material/src/styles/themes or material/src/styles/main with @use … with
    before loading other material theme modules. all (the default) emits explicit medium/high
    contrast attributes and the system contrast preference; explicit emits only the attribute
    rules; preference emits only the preference rules; none emits neither. Every mode keeps
    standard light and dark roles. In the twelve-theme bundle, none saves 117,184 raw bytes and
    8,177 gzip bytes (level 9) compared with all, by dropping both contrast activation families.
    Default output and prebuilt CSS are unchanged; other Sass configuration remains unsupported.

  • <m-text-field> accepts trailing-icon-label to name its trailing icon button (#37).
    With a non-empty label, the icon is a button that dispatches trailing from the host on click,
    Enter or Space. The event bubbles and is composed; its detail is { value, event }, carrying
    the field's current value and the original activation event. Changing the label updates the
    existing button in place and keeps the input and focus. A missing or empty label leaves the
    icon decorative: an aria-hidden span, outside the accessibility tree.

Documentation

  • Added MIGRATING.md, verified against published 3.0.5, and linked it from both READMEs (#97).

npm: material@3.1.0 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.0.6

Choose a tag to compare

@github-actions github-actions released this 16 Oct 15:01
197c1c2

Fixed

  • An extended FAB in a collapsed navigation rail's header shrinks to its icon button instead of
    being clipped (#93).

    A collapsed rail is 96 px wide, but an extended FAB placed in its header kept its expanded
    width — padding, gap and a label up to 280 px — so the rail's edge cut it off and the label
    read "Ad…". This held for a FAB built by the factory as well as for an <m-extended-fab>
    element slotted into the header, because the element draws its button inside its own shadow
    root. The rail's header now hands the collapsed state down as an inherited CSS custom property
    that the extended FAB reads, so the button drops its padding, gap and label, stays at its size's
    height, and fits inside the rail in both text directions. Expanding the rail restores the full
    FAB. The FAB keeps compacting when its element is upgraded, replaced or rebuilt, and it fires
    no new events; a FAB method you call yourself keeps the events it had. A standalone extended FAB
    with the --collapsed modifier draws the same compact button.

npm: material@3.0.6 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.0.5

Choose a tag to compare

@github-actions github-actions released this 09 Oct 06:53
7ae7cd1

Changed

  • package.json adds five keywords: material 3 expressive, m3 expressive, expressive motion, spring motion and motion. They are what npm's search reads. The library follows
    the Material 3 Expressive tokens for sizes, shapes and colours, and takes the spring motion
    tokens where a component's design uses them. The description, the README and the code are
    unchanged.

Fixed

  • A discrete slider's handle rests on the end stop at either end of the track, not past it
    (#79).

    A discrete slider sets its stops in from the ends of the track by the end's own corner radius —
    8, 12, 16 or 28 px at XS to XL — so its first and last stops sit inside the pill. The handle's
    position dropped that inset at the first and last values, so at the minimum and the maximum its
    centre sat on the track's very edge, a corner radius past the end stop, with the handle
    straddling the end of the pill. Every value position now takes the inset, the ends included: the
    handle's centre lands on the end stop, and the filled track stops with it, keeping the handle gap
    it keeps everywhere else. A continuous slider spans the whole track as before; the interior
    stops, every size and right-to-left are unchanged.

  • A toolbar's overflow button draws its icon in the toolbar's own colour, not black (#81).
    The overflow icon's SVG carried no fill, so it painted the SVG initial black in every variant
    and both themes, while the toolbar's other icons follow the button through
    fill="currentColor". On a dark or coloured toolbar the overflow button showed black dots. The
    icon now carries currentColor, so it takes the colour its siblings take, in every theme and
    under a custom color. A custom overflowIcon is drawn as you give it.

  • A card whose header is its last child keeps the bottom padding of its other three sides
    (#81).

    A card header takes the card's 16 px padding on the top, left and right and no bottom padding, so
    content or actions can follow it flush. A card that was only a header — no content, no actions —
    kept that zero, and its title and supporting text sat on the card's lower edge. A header that is
    the card's last child now keeps the same 16 px at the bottom as on the other sides; a header
    followed by content or actions stays flush.

  • A tooltip outside the top layer is placed correctly on a scrolled page (#84).
    The surface is fixed to the viewport in every path and takes the target's viewport rectangle,
    but the placement added the document's scroll offset to it: on a page scrolled 300 px, the
    tooltip sat one scroll distance away from its target, and a page scrolled sideways pushed it
    aside the same way. The placement no longer adds the scroll, so the surface lands on its
    target at any scroll position, in every placement. The top-layer path, the viewport clamps and
    the transitions are unchanged.


npm: material@3.0.5 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.0.4

Choose a tag to compare

@github-actions github-actions released this 06 Oct 06:08
92dd5f0

Fixed

  • A tooltip's arrow is the colour of the tooltip's own surface, in every variant and both
    themes (#77).

    The arrow grows out of the container, so it has to read as that surface. A rich tooltip paints
    surface-container, but its arrow kept the base inverse-surface; a plain tooltip paints
    inverse-surface, but its arrow still painted the surface-container-high of a surface the
    variant had dropped. On each of the twelve placements that draw an arrow, the arrow showed as a
    triangle in a colour that was not the tooltip's own. Every direction now takes its colour from
    the variant's surface — inverse-surface by default and for plain, surface-container for rich.
    Geometry, transitions and the surfaces themselves are unchanged.

  • An uncontained carousel lays every item out at the configured item size, stepping by
    itemWidth + gap and flowing past the container's edge (#78).

    The uncontained layout was placed through the multi-browse keyline model: after the first one or
    two items, slides were scaled down toward a 10 px anchor, so each showed as a sliver a few pixels
    wide and consecutive items stepped 28.8 px apart instead of 288. That bunched the tail of the row
    into a fraction of its width and left the row ending well before the container's right edge.
    Items are now laid out at the configured width, at itemWidth + gap steps, and cut only by the
    container edge — never shrunk into a sliver. How much of an item the edge leaves showing depends
    on the container width and the scroll position. An item that sits fully past the edge stays laid
    out and is not hidden. Snap positions are those steps, so a slide's scroll offset is its index
    times itemWidth + gap; the scrollable extent is wider, because the row is now as long as its
    slides. The contained variants — multi-browse, hero, center-aligned hero and fullscreen — keep
    their keyline layout, and under reduced motion they behave as before.


npm: material@3.0.4 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.

material 3.0.3

Choose a tag to compare

@github-actions github-actions released this 05 Oct 04:35
17bd9b6

Fixed

  • A selected toggle icon button inside a toolbar keeps its round shape.
    A standalone toggle morphs round to square when selected (icon buttons specs),
    and toolbar items inherited that morph, so the selected Bold of a formatting
    toolbar turned a rounded square — a shape the toolbars specs warn against in
    the floating container. Selection inside a toolbar is shown by colour roles
    alone (the toolbar token set has no item shape tokens), so the round radius
    now stays in every state. Extending that to the pressed state — and to docked
    as well as floating toolbars — is this library's choice (round stays round),
    not something the specs ask for. The pin is each item's own resting radius,
    on the bar's direct items only, whatever the tag prefix the elements were
    registered under — the toolbar's sheet matches the marker attribute every
    icon-button host of this library carries. The radius is right at first
    paint on the paths the checks cover: elements created in the browser,
    renderElement() output, and the server render of the React, Vue, Svelte
    and Solid adapters, which all write the marker into their markup. Marker-less
    markup still pins under the default m- prefix, where the sheet can spell
    the tag. Standalone toggles, square buttons, the overflow menu's content and
    explicit --mtrl-button-shape-* overrides are unchanged.

  • An internal marker attribute, data-mtrl-icon-button, now appears on icon buttons; it carries no public API.


npm: material@3.0.3 · Docs: md3.io · Full history: CHANGELOG.md

The history before 3.0.0 was developed in floor/mtrl; #numbers in commit subjects before 3.0.0 refer to pull requests there.