Repository navigation
This release removes the aliases kept for markup written before 0.8.0. See
Removed and Migration below before upgrading.
Deprecated
- Chip sizing properties
--padding,--font-sizeand--font-size-icon.
Use--md-comp-chip-padding,--md-comp-chip-label-sizeand
--md-comp-chip-icon-sizeinstead. Each new property takes priority over
its old counterpart. Old overrides on a chip remain supported as fallbacks,
defaults are unchanged, and there is no removal date.
Added
-
Native multiple-leaf TreeSelect through
<select data-tree multiple>, with
independent leaf toggles, an open popup and optional search retaining hidden
choices. Native form submission includes all selected leaves and no branches.
Branch defaults never propagate into leaf choices; reset and refresh retain
native ownership. Selection and focus remain separate on the named tree. -
Opt-in single-leaf TreeSelect search through
searchable: true. Matching leaf
paths open without changing the native selection. Clearing or closing restores
pre-query expansion, including through refresh and disabled-control recovery.
Search reuses accessible popup controls, localization and lifecycle handling
while retaining tree keyboard behavior and native leaf submission. -
Independent drop-zone file-count limits through
data-max-files. Exact-count
choices pass; too many files invalidate the complete retained selection with
accessible count feedback andexpressive:rejectreasoncount. All selected
files appear in independent rejection snapshots. Shared Forms handling keeps
type/size precedence, application validity, deduplication and reset recovery. -
Optional per-file drop-zone byte limits through
data-max-size, independent
of type checking. Exact-limit files pass; one oversized file invalidates the
whole retained selection with accessible feedback andexpressive:reject
reasonsize. Combined rules preserve type-first feedback and application
custom validity. Constraint-aware deduplication, reset and shadow handling
reuse the existing Forms path. -
Opt-in drop-zone type validation with
data-validate-types, native form
prevention and accessible error feedback. Extensions, exact MIME types and
standard media wildcards match whole selections without filtering files.
Browse and drop share validation;expressive:rejectreports invalid choices.
Recovery preserves application validity and supports open shadow roots. -
File reset cleanup now follows native form ownership, including external
associated inputs, and runs after reset dispatch/default handling. Canceled
resets preserve current file state; newer selections retain their feedback. -
Server-owned data table paging through silent validated
DataTables.setPage
state and cancelableexpressive:pagerequests. Server responses retain
every supplied record without local slicing, filtering or sorting. Local
page and size controls now request before applying their default; cancel
to preserve the accepted view. Null state restores local paging. -
Authored single-select page-size controls for local data tables. Changing
size preserves an available page or clamps to the new last page, retaining
selections and matching counts. -
Local data table paging through a fixed
data-page-sizeand authored
Previous/Next buttons. Paging follows filtering and sorting, keeps details
with records, scopes select-all to the displayed page and preserves
selections on other pages. Silent sync clamps the current page after row
changes. Ordinary pagination links retain their navigation behavior. -
Server-owned data table filtering through cancelable
expressive:filter
requests with a trimmed, case-preserving query. Cancel synchronously to
preserve the current view, then replace records and call
Expressive.DataTables.sync(wrapper). Sync stays silent and preserves the
application's server count; an uncanceled input resumes local filtering. -
Opt-in data table text filtering. A labelled search input with
data-table-filtersearches columns whose headers havedata-filter.
Record details stay together, author-hidden rows remain hidden, and an
authored status output can announce the result count. Select-all now
affects only enabled records visible in the current view; hidden records
retain their selections. To include hidden records in a bulk operation,
check them from script and callExpressive.DataTables.sync(wrapper);
expressive:changecontinues reporting all selected records. -
Opt-in select filtering with
FormSelect'ssearchableoption. A
separate popup search field filters grouped options while the native select
retains its form value. Includes localized search and no-results text,
keyboard navigation, query clearing on close and reset, and query-preserving
refresh. On a multiple select, picks keep the popup open with the query and
focus in place, and options the query hides stay selected and submitted.
Default, tree and browser-default selects keep their behavior. -
Autocomplete option groups. An entry's
grouplists it under a labelled
heading, and entries with the same group gather where the group first
appears. Up and Down pass over headings, the announced result count leaves
them out, and a click on one chooses nothing. Entries without a group keep
their behavior. -
Disabled Autocomplete entries. An entry with
disabled: truestays in the
results, dimmed, and a click, Enter andselectOption()leave it
unselected, or selected when theselectedoption or
setMenuItems(items, selected)chose it. Up and Down skip it, and the
announced result count leaves it out. Entries without the flag keep their
behavior. -
Independent gap utilities.
.gap-x-0through.gap-x-6set column gaps and
.gap-y-0through.gap-y-6set row gaps on the existing rem spacing scale.
Both support all four window-size prefixes and override the gap shorthand
at the same breakpoint. Existing gap utilities retain their behavior. -
Responsive child sizing.
.flex-1,.flex-noneand.shrink-0now take
window-size prefixes. New.flex-initialand.shrink-1classes restore
content sizing and shrinking, with the same four prefixes. -
Responsive self-alignment.
.self-start,.self-center,.self-endand
.self-stretchtake themedium:,expanded:,large:and
extra-large:prefixes, and the new.self-autoreturns a child to its
parent'salign-items.self-center expanded:self-autocenters a child
until Expanded and then follows the parent. The unprefixed classes are
unchanged. -
Responsive visual order.
.order-first,.order-lastand the new
.order-none(order: 0) take themedium:,expanded:,large:and
extra-large:prefixes.order-last expanded:order-nonemoves a child to
the end until Expanded and then back to its source position. The classes
change visual order only; Tab and reading order still follow the source. -
Show more. A
.show-moreholds text with aline-clamp-1,-2or
-3class and a<button>; the button shows the full text, reads
"Show less" while it is open (i18n.showLessordata-show-less) and
setsaria-expandedandaria-controls. The button hides while the
clamped text fits and comes back when a resize or a web font cuts it.
New semantics rules require a<button type="button">and clamped text. -
Treemap chart. A
<figure class="treemap-chart">draws each row of its
table's first value column as a rectangle whose area is its share of the
total, filling the plot in a squarified layout from the largest value
down, mirrored in right-to-left pages. Each rectangle shows its name and
cell when they fit, and rows with no positive value draw nothing. The plot
is one Tab stop whose arrow keys step through the rectangles in table
order, the tooltip adds each row's share, and it takesupdate()and
data-watchlike the other charts. A new semantics rule asks for a
<figure>with a<table>and a<figcaption>. -
Hover card. A link with
hover-card-triggernames a.popoverwith
popover="hint"inaria-details, and once the pointer or keyboard
focus has rested on the link for 500 ms, that card shows against it.
The card stays while the pointer or focus is on the link or the card,
and hides 300 ms after both are left, or at once on Escape with focus
back on the link. A tap or click follows the link and shows no card.
Two new semantics rules keep the trigger an<a href>whose
aria-detailsnames a[popover]. -
Transfer list. A
.transfer-listholds a.transfer-availableand a
.transfer-chosenlist of checkbox rows withdata-transferbuttons
between them.addandremovemove the checked rows,add-alland
remove-allevery row, and a row moved back returns to its original
place. The form submits the chosen rows' values under the container's
data-name, in the chosen list's order, and a form reset puts the rows
back. Each move firesexpressive:transferwith{ rows, from, to }
and is announced throughTransferList.i18n.moved. Four new semantics
rules ask fordata-name, unnamed checkboxes, named lists and
type="button"on the move buttons. -
Funnel chart. A
<figure class="funnel-chart">draws each table row as
a stage, from the top down in table order, centered and as wide as its
value against the largest one. The tooltip adds each stage's percentage
of the first row, and further columns show in the tooltip. The plot is
one Tab stop whose up and down arrows move between rows, and it takes
update(), where stages move from their old widths, anddata-watch
like the other charts. A new semantics rule asks for a<figure>with a
<table>and a<figcaption>. -
Sankey chart. A
<figure class="sankey-chart">draws each table row as
a flow: the first column names the source node, the second the target
node, and the third holds the value. Nodes stand in columns by their
longest path from a source and stack in the order they first appear.
Each link is as thick as its value, and each node as tall as the larger
of its in and out totals. Rows without a positive value or both names,
and rows that would close a cycle, draw nothing. The plot is one Tab
stop whose arrow keys step through the links in table order, and it
takesupdate()anddata-watchlike the other charts. A new
semantics rule asks for a<figure>with a<table>and a
<figcaption>. -
Loading overlay. While the page sets
aria-busy="true"on a
.loading-overlay, a surface scrim covers it, its loading indicator
shows in the centre, and the script makes the regioninert, so
nothing inside takes a click, a tap or keyboard focus. A visually
hidden status region after it announces the start and the finish, in
words set withi18n, and focus goes back to the control that had it
when the region clears. A new semantics rule asks for a direct
.loading-indicatorchild. -
Dialogs.confirm()asks a yes-or-no question in a basic alert dialog
and returns a promise that resolvestrueon the confirm button and
falseon Cancel, Escape or a scrim tap. Focus starts on Cancel and
returns to the opener, and the dialog is removed once it closes.
Dialogs.i18nholds the default labels, "OK" and "Cancel". -
.back-to-topis an in-page link that appears once the window has
scrolled pastthreshold, one viewport height by default. Activating it
scrolls to the top, instantly under reduced motion, and focuses the
element itshrefnames. TheBackToTopcomponent keeps itinert
while hidden and watches the threshold with anIntersectionObserver.
Without the script the link stays visible and jumps to its target. A
focused link stays until focus leaves, and modified clicks are left to
the browser. -
.defer-rendermakes the browser skip layout and paint for a long
list's off-screen rows throughcontent-visibility: auto. Skipped rows
reserve--defer-render-row-size, the list item height by default, and
focus rings inside a deferred row are drawn inset so the row does not
clip them. Rows with swipe actions are left alone, and table rows are
not supported. -
Line clamp utilities.
.line-clamp-1,.line-clamp-2and
.line-clamp-3cut text to that many lines and end the last one in an
ellipsis, and.line-clamp-noneshows every line again. All four take
themedium:,expanded:,large:andextra-large:prefixes. -
Pointer helpers.
.pointer-events-nonelets clicks, taps and hovers
pass through an element to what lies beneath it, and
.pointer-events-autogives them back to a child, such as a button in
a caption overlay..cursor-pointershows the pointer cursor and
changes nothing else. Neither pointer-events class affects focus or
keys; usedisabledorinertto block all input. -
.opacity-0,.opacity-25,.opacity-38,.opacity-50,.opacity-75
and.opacity-100set an element's opacity. 38 is the M3 opacity for
disabled content. -
.list-unstyledremoves a<ul>or<ol>'s markers and inline-start
padding and keeps its margins. A new semantics rule,
list-unstyled-has-list-role, flags one with norole, because WebKit
drops the list role from a list without markers. Addrole="list", or
the role the list should have. -
Position and inset helpers:
.position-absolute,.position-fixedand
.position-static,.inset-0, and the edge pins.top-0,.bottom-0,
.start-0and.end-0. The start and end pins use logical properties, so
they swap sides in right-to-left pages. The classes take aposition-
prefix because the navigation bar, toolbar and table of contents already
use.fixedas a modifier. They have no window-size prefixes, and
.position-fixedsets noz-index. -
.grid-auto-filllays out as many columns as fit, each at least
--grid-column-minwide (15rem by default). Set the property on the grid or
an ancestor. A container narrower than the minimum gets one column, and the
last row keeps the column width. Pair it with.gap-*. -
Data table column resize. A
<button class="drag-handle">ending a
header cell lets people resize its column by dragging, with the arrow
keys (16px, 64px with Shift), Home and End, or a click, Enter or Space
that steps through narrow, middle and wide. Each change fires
expressive:resizefrom the wrapper with{ header, column, width },
andDataTables.setWidth()restores saved widths without firing it. The
new--md-comp-data-table-column-min-widthtoken sets the narrowest
width. -
Data table column visibility. A menu checkbox item with
data-column
set to a first-row header cell'sidhides that column's header and
cells while it is unchecked, and an item unchecked at load starts its
column hidden. Cells that span several columns stay, and resized columns
keep their widths.DataTables.sync()applies the toggles for markup
rendered later. The framework stores nothing; save the choice from the
menu'sexpressive:change. -
.sticky-columnon a.data-tablekeeps the first column in view while
the other columns scroll sideways, in both writing directions. When the
first column holds row checkboxes, the name cell after it stays pinned
too, and the checkbox column takes the width of the new
--md-comp-data-table-checkbox-column-widthtoken. Pinned cells keep
their row's hover and selected colors, and a divider marks their edge. -
Calendar week, day and year views.
table.calendar.weekand
table.calendar.dayare hourly grids: a row per hour named by a
<th scope="row">, and a column per day whose header can carry
aria-current="date". An event sits in the cell for its day and
starting hour and covers one hour, or--md-comp-calendar-event-duration
hours, starting--md-comp-calendar-event-starthours into the row.
The placement is only visual, so write each event's time in a
.visually-hiddenspan. Events that overlap in time overlap on screen,
and an event past the last hour row spills out of the table..calendar-yearlays
out twelve month tables in as many columns as fit. The new
--md-comp-calendar-hour-heighttoken sets the row height (48px). -
AutoInit(context, options, { watch: true })keeps watching the
context after the first scan. Components start in markup added later,
with the same options, markup options andno-autoinitopt-out, and
are destroyed when their markup is removed. Elements that already have
an instance keep it, moved elements keep theirs, and shadow roots are
watched only when passed as the context. The call returns a function
that stops watching. Withoutwatch,AutoInit()behaves as before. -
Timepicker's
minTimeandmaxTimeoptions bound the times a person
can choose, as 24-hourHH:MMorHH:MM:SS. Dial ticks outside the
range are dimmed and refused, the fields stop stepping at the bounds and
refuse typed times outside them, and a value outside the range opens at
the nearest bound. On a 12-hour clock, a period with no allowed time has
its AM or PM button disabled. Ranges can't cross midnight. Without the
options every time stays open. -
Timepicker's
showSecondsoption adds a seconds field after the
minutes. It is a spin button named by the newi18n.seconds, it steps
and takes typed values like the minute field, and the picker writes
HH:MM:SS, with AM or PM on a 12-hour clock. The dial keeps its hours
and minutes views. Atype="time"input needsstep="1"to hold the
seconds without a step mismatch. The default offalsekeepsHH:MM. -
AutoInit()reads options from markup. An element's
data-<name>-optionsattribute holds a JSON object of that component's
options, with the name in kebab case:data-menu-options,
data-form-select-options. Options passed toAutoInit()win for the
keys they set. Values must match the kind of their default, so callbacks,
elements andnull-default options stay script-only, as do
allowUnsafeHTMLand the Chips and Autocompletedatalists. Invalid
values log a warning and keep their defaults. Directinit()calls do not
read the attribute. -
Tooltip's
openOnClickoption opens the tooltip on a click instead of
hover and focus. A second click, Escape, a click elsewhere or focus moving
to another element closes it, and Enter or Space on a button trigger
toggles it. On touch a tap is the click, so the long press is off. The
default offalsekeeps hover and focus. -
A chart's
update()moves its marks from the old values to the new ones.
Line chart lines, areas and points, column chart columns, bar chart bars
and mixed chart marks move over--md-sys-motion-duration-medium4with
--md-sys-motion-easing-standard. A series or value new to the chart
rises from its base, and one that goes away goes at once. With reduced
motion the new shape appears at once. Migration: afterupdate(),
columns and bars no longer grow again from zero, and lines no longer jump
to their new shape. No page change is needed. -
Autocomplete's
allowCreateoption ends the list in anAdd "text"row
when the typed text matches no suggestion exactly, ignoring case and
surrounding spaces. Choosing it with Enter or a click callsonCreatewith
the trimmed text and firesexpressive:createwith{ text }. The
selection does not change; the page adds the entry.i18n.createsets the
row's label. Chips add the text as a chip, in anautocompleteOnlyfield
too. -
When a search returns no suggestions, Autocomplete's open list shows
i18n.noResultsas its first row, so sighted users see what the live
region announces. The row isli.autocomplete-no-resultswith
role="none"andaria-hidden="true". Arrow keys skip it, and a click on
it chooses nothing and keeps focus in the field. It leaves the list when a
search returns a match. Arrow keys and Enter now move through
li[role="option"]rows only. In Chips, Enter that adds the typed text
as a chip now closes the list. -
A chart with
data-watchon its figure, or thewatchoption, redraws
itself when its table changes, so page script no longer callsupdate().
It watches added and removed rows and cells, cell text, anddata-value,
classand the heatmap'sdata-labelinside the table, and it follows a
replaced table. The edits a script makes in one run redraw it once. Every
chart type takes it, anddestroy()stops it. Charts without it behave as
before. -
Autocomplete's
searchDelayoption waits that many milliseconds after
typing pauses before callingonSearch, so a remote search runs once per
pause instead of once per key. A text change aborts the running search's
signal right away. A search on focus runs at once, and blur,destroy()
or choosing a single result cancels a pending one. The default of0
keeps the search on every input. Chips take it through
autocompleteOptions. -
A menu item with
role="menuitemradio"checks itself and unchecks the
other radio items in its group. A group is a<ul role="group">with an
aria-labelinside an<li role="none">; radio items outside a group form
one group per menu. Rows in a group are styled and reached by arrow keys
and type-ahead like any other row. A click or Enter checks the item and
closes the menu whilecloseOnClickis on. Space now checks it and keeps
the menu open, where it used to close the menu. A change fires
expressive:changefrom the trigger with{ item, checked: true }, after
onItemClickandexpressive:select; choosing the checked item fires no
change. Page code that syncedaria-checkedon radio items can go. It
writes the same values, so leaving it in place does no harm. -
A menu item with
role="menuitemcheckbox"toggles its own
aria-checked. A click or Space flips it and keeps the menu open, so
several items can be toggled in a row; Enter flips it and closes the menu
whilecloseOnClickis on. Each toggle firesexpressive:changefrom the
trigger with{ item, checked }, afteronItemClickand
expressive:select. Migration: a page that flippedaria-checkeditself
inonItemClickor a click listener must remove that code, or the two
flips cancel out. -
A
.validatefield setsaria-invalid="true"along withinvalid, and
clears both once the value passes, so a screen reader hears the error
state the color shows. Its[data-error]supporting text joins the
input'saria-describedby, keeping the ids already there, so the error
message is read with the field. An emptydata-lengthno longer marks a
field invalid. -
Tooltips show on a long press on touch, as Android does: after half a
second held, withoutenterDelay, staying for a second and a half after the finger lifts. A
tap used to show the tooltip through its compatibility mouse events and
leave it up until the next tap elsewhere; a tap now shows nothing, and a
press that turns into a scroll shows nothing. The click a long press's
release sends is consumed, so it does not press the button or follow the
link. -
Menus take
placement: 'top'to open above the trigger when they fit
there, as for a trigger in a bottom bar, and Home and End jump to the
first and last rows. An anchored menu that does not fit below its trigger
now opens above it when there is more room there. Its height was capped
to the room below, so it shrank and scrolled instead of flipping. -
Chips submit a form value. Give the field's
<input>anameand the
chips submit under it, oneidper chip, as a multiple select does.
initwraps the input in<expressive-chips-control>, the framework's
first custom element. It is form-associated, hasdisplay: contentsand
takes the name, so half-typed text is not submitted. The browser handles
the field's reset, validity and disabled state: a reset puts back the
starting chips within the reset, and a disabled fieldset drops the values
at once. Settingdisabled,readonlyorformon the input applies at
the next microtask. Arequiredinput makes the field valid once it has a
chip, and says the newi18n.requireduntil then, with
aria-required="true"on the input. That validity is on the control,
apart from any error the page sets on the input. A read-only input's field
is not required.destroy()unwraps the input and gives its name and
requiredback. Without form-associated custom elements, as in jsdom, the
input keeps its name andrequiredand submits its text. Pasting text
that lists values, split by commas, tabs or new lines, adds a chip for
each, up tolimit. -
Data tables sort and select. With
data-sorton a.data-table, a
button in a header cell reorders the rows by its column and moves
aria-sort. Anumericcolumn compares numbers, other columns compare
text in the page's language,data-valuestands in for a cell's text, and
a detail row moves with its row. A cancelableexpressive:sortlets a
server-paged table sort the rows itself. A checkbox in the first header
cell now selects every enabled row and shows mixed for some, and each
change firesexpressive:changewith the selected rows.
Expressive.DataTables.sync()updates the select-all box after rows are
checked from script. Pages that sort with their own script and no
data-sortare unaffected. -
Charts with a series legend take
.toggle. Each legend item becomes a
button witharia-pressedthat shows or hides its series, and the scale
refits to the series left. The other series keep their colors, the last
one shown with data cannot be hidden, and a hidden series stays hidden
throughupdate(). Line, column, bar, mixed and radar charts have it.
Without the class the legend stays decorative. -
The Auto Init page documents Alpine.js. A short
x-expressivedirective
starts a component each time Alpine renders its element, including inside
x-ifandx-for, and destroys it when Alpine removes the element. The
page also shows how to keep a Select in sync withx-modelby calling
refresh(). A browser test runs the directive against Alpine in Chromium,
Firefox and WebKit. -
Charts have
update(), which redraws a chart from its changed table in
place. Before, the way to redraw was to callinit()again, which dropped
keyboard focus and the highlighted row. Now focus stays on the chart, the
highlighted row stays where the table still has it, and a scrolled heatmap
stays scrolled. A chart set up with an empty table draws once it has rows. -
Tabs can be a tablist. A strip the page writes as
role="tablist", with
role="tab"links naming their panel inaria-controls, gets the ARIA
tabs pattern: Tabs keepsaria-selectedand a rovingtabindex, so the
strip is one Tab stop, and Left, Right, Home and End select as they move.
Link tabs witharia-currentstay the default.semantics.jsonallows the
roles only when the markup is complete: named, on a<div>, every tab a
role="tab"witharia-controls. In either mode an overflowing bar
scrolls the selected tab into view, without scrolling the page, and with
no.activetab or matching hash the first enabled tab is selected rather
than a disabled first one. -
Scatter chart. A
<figure class="scatter-chart">plots each table row
as one point per series: the second column holds the x value, and each
further column is a series of y values.data-x-minanddata-x-max, or
thexMinandxMaxoptions, fix the x scale, andaxislabels both
scales. The plot is one Tab stop whose arrow keys step through the points
in table order, and it takesupdate(),data-watchand atoggle
legend like the other charts. A new semantics rule asks for a<figure>
with a<table>and a<figcaption>. -
Bubble sizes on the scatter chart. With
bubble, the last column sizes
each row's points, so their areas follow its values, and the largest is
--md-comp-scatter-chart-bubble-size(48px) across. A row without a
positive size draws no points. The tooltip names the size, atoggle
legend refits the sizes, and bubbles grow or shrink from their old sizes
afterupdate(). Room around the plot keeps a bubble at the end of a
scale clear of the axis labels and legend. -
Pull-to-refresh. A touch pull down from the top of a
.pull-to-refresh
container, or its<button data-refresh>, firesexpressive:refreshand
shows the container's loading indicator witharia-busy="true"until
every promise passed toevent.detail.waitUntil()settles or the page
callscomplete(). Thethresholdoption sets the pull distance (64px).
A visually hidden status region announces the start and the finish, in
words set withi18n. The container and the page holding it get
overscroll-behavior-y: contain, so the browser's own pull-to-refresh
stops. New semantics rules
ask for the button and a direct.loading-indicatorchild. -
Swipe actions. In a
.list.swipeable, a touch swipe toward a row's
inline start slides it and reveals its.swipe-actionsbuttons. A swipe
only reveals; nothing runs until a person presses an action. The row's
<button data-swipe-toggle>opens and closes it without a swipe, focus
on an action opens its row, and Escape, a tap outside or an action closes
it. One row is open at a time. Each change firesexpressive:swipetoggle
with{ row, open }and runsonToggle;open(row)andclose()work
from script. A new semantics rule asks for the toggle in every row with
actions. -
Input mask. A text field with
data-maskformats what people type,
paste or compose to the pattern:0takes a digit,aa letter,*
either, and other characters are literals the mask writes, such as
(000) 000-0000. The caret stays after the edit, Backspace and Delete
remove the digit across a literal, an IME composition is formatted once
it ends, and saved and reset values are formatted too. The form submits
the value as shown. A new semantics rule keepsdata-maskon input types
that have a caret. -
Currency input. A text field with
data-currency="EUR"shows its
amount in the page's locale and that currency once it loses focus, such
as1.234,50 €underlang="de", and the plain number1234,5while
it has focus. The last.or,is the decimal mark when no more than
the currency's fraction digits follow it, so1234.5and1.234,5read
the same. The form data holds the plain number,1234.5. Text with no
digit stays as typed and marks the field invalid, with a messagei18n
can replace. The instance'svaluereturns the number. A new semantics
rule keepsdata-currencyon text inputs. -
Cascader. A
.field.cascaderwith a named text input and nested
ul.cascader-optionslists shows the options as columns in a popover,
where each choice opens the next column. Arrow keys, Home, End, Enter,
Space, Escape and Tab move through the columns, swapped underrtl.
Choosing a leaf shows the path in the input and firesinputand
change, and the form gets one entry per level, such asplace=us,
place=ca,place=sf, through a form-associated control.data-selected
on a leaf sets the starting path, which a reset brings back, and
requiredblocks submit until a path is chosen. New semantics rules keep
the source lists plain and every option valued. -
Tree select. A
<select data-tree>whose options name their parent's
value indata-parentopens an ARIA tree, where the arrows, Home, End,
*and typeahead move between visible rows and only a leaf can be
chosen. The native select keeps the form value, reset and validity, and
branch options are disabled while it runs, so the form never submits
one.refresh()keeps open branches and focus. AutoInit starts
TreeSelectonselect[data-tree]and FormSelect on other selects, and
a new semantics rule keepsdata-treeon single selects nested only
throughdata-parent. -
A
data-draggablemap marker moves without a drag (WCAG 2.5.7). A tap
picks it up and setsaria-pressed, and the next tap on the map drops it
there.Escapeor a second tap puts it back, and the arrow keys nudge a
focused one. A marker that is not a<button>getsrole="button"and
tabindex="0"while the map runs. -
Map controls take
pan-up,pan-down,pan-leftandpan-rightactions,
which move the view a third of the map's size, so it pans without a drag. -
Menu, Lightbox, NavigationRail, Cards and ExpandingCard fire
expressive:openandexpressive:closeDOM events from the element
they were initialized on. Menu, Lightbox and NavigationRail then fire
expressive:openedandexpressive:closed. The events bubble and cross
shadow roots, and each follows itson…callback, which still works. -
Choices and changes fire events too, with what was chosen in
detail:
expressive:changefrom Tabs ({ panel }) and Carousel
({ item, dragged }),expressive:selectfrom Menu items ({ item }),
Datepicker ({ date }) and Timepicker ({ hours, minutes }),
expressive:confirmandexpressive:cancelfrom both pickers,
expressive:autocomplete({ values }), andexpressive:chip-add,
chip-selectandchip-delete({ chip }). A callback that changes the
choice again leaves only the event for the final one. Tabs now calls
onShowafter hiding the old panel, and Chips callbacks get the instance
asthis, as every other component's do. -
The rest report too: Tooltip fires
expressive:openand
expressive:closewhen it shows and hides; Snackbar firesopen,
action,closeandclosed; ButtonGroup fireschangewith
{ button, pressed }; ScrollSpy fireschangefrom the section with
{ link }; Tour firesopen,changewith{ step, index }andclose
with{ completed }; and copy buttons fireexpressive:copywith
{ text, copied }.Tour.end(el, completed)takes the flag from script. -
Sheets spring back with the finger's speed: a bottom or side sheet
released short of dismissing solves its spring at run time from where
it is and how fast it moves, and a press while it springs catches it in
place. Each motion spring gains-dampingand-stiffnesstokens. -
Arabic, Hebrew and Thai text: Noto Sans Arabic, Noto Sans Hebrew and
Noto Sans Thai ship at 400, 500 and 700, each limited to its script's
unicode-rangeso a page fetches one only when it shows that script,
and--md-ref-typeface-fallbacklists them beforesans-serif. -
Autocomplete
onSearchgets anAbortSignalas its third argument. It
aborts when a newer search starts or the autocomplete is destroyed, so a
page that passes it tofetch()drops stale responses. -
Linear progress
.stopputs a dot in the indicator colour at the
track's end, and.wavydraws the indicator as a wave over a thin track,
on<progress>,.determinateand bare bars, and a circular indicator
takes.wavytoo: a wave round the ring over a thin track. New tokens:
--md-comp-progress-stop-sizeand--md-comp-progress-wavelength. -
Common button shapes:
squaretakes the size's squared corner, every
common button morphs to its size's pressed corner while pressed, and
aria-pressedmakes a button a toggle that swaps round and square when
selected and takes the M3 selected colours for its style. -
Questionnaire extras, each opt-in on the form:
data-shortcutslets a
letter or digit pick an answer,data-summaryadds a review step that
lists every answer with an Edit button before submitting, and
data-save="key"keeps the answers and the shown question in
localStorage until the form is submitted. -
Keypad and keyboard keys:
data-key="CapsLock"holds capitals until
pressed again, anddata-key="Layer"shows the elements whose
data-layermatches itsvalue, such as a row of symbols. A hardware
key held down in the field presses the matching on-screen key. The
keypad now checksminlengthon its own edits, with the
i18n.tooShortmessage. -
Radial chart fills:
data-segmentssplits each ring into segments filled
whole, and.gradientshades each fill along its ring, from a light
tint at the start to its full color where the fill ends. -
Range columns and bars: a series header with the class
rangereads
cells such as120–480,3 to 14ordata-value="120 480"and floats a
column or bar between the two ends, rounded at both. -
Mixed chart stacking:
.stackedstacks the column series into one
column per row, with theendscale's columns in their own column
beside it. Lines and areas keep their own values. -
Line and mixed chart
.pointsmarks every value on a line with a dot,
and.straightdraws straight lines between values instead of a curve. -
Heatmap scales:
data-midmakes the scale diverge from that value, with
values below it shading towards the new
--md-comp-heatmap-chart-low-color(tertiary), anddata-stepsgroups
the shades into bands, with a swatch per band under the grid. -
Wide heatmaps scroll: cells keep
--md-comp-heatmap-chart-cell-min-width
(12px), and a grid too wide for its figure scrolls its cells sideways
instead of narrowing them, while the row labels stay in view. The arrow
keys scroll the cell they move to into view. -
Pie chart grouping:
data-other="5"turns every row under 5% of the
whole into one last slice with their sum and share, named by
data-other-label("Other" by default). The table keeps every row. -
Radial chart names:
.labelsdraws the rings three quarters of the way
round and writes each row's name in the empty top left quarter, beside
its ring's start. Names shrink to fit crowded rings. On a gauge, the names
hang under the base line, a row per ring with the innermost on top, each
after a line down from its ring's start. A sparkline ignores it. -
Pie chart names:
.labelswrites each slice's row name outside the pie,
beside its slice. Names that would touch move apart along their side, a
line leads from each name back to its slice, and the pie shrinks to leave--md-comp-pie-chart-label-width(96px), or a
quarter of a narrow figure, on each side. -
Expandable bottom sheets:
dialog.bottom-sheet.expandableopens at
--md-comp-bottom-sheet-partial-height(50% of the viewport) and
.expandedgrows it to the full height. A drag up expands it, a drag
down collapses it before it dismisses, and a handle<button>switches
between the heights witharia-expandedkept current. -
Arrow keys for tabs and lists: Left, Right, Home and End move focus
between tabs, wrapping and skipping disabled ones, and Up, Down, Home and
End move it between the rows of a.listwhose rows are each one link or
button. Tabs and rows stay links and Tab stops; focus moves without
choosing. The list keys come from the newListsbehavior. -
Modal date and time pickers:
displayPlugin: 'modal'opens the picker in
a modal<dialog class="display-modal">drawn as a basic dialog, named by
the input's label. Ok writes the input; Cancel, Escape and a press on the
scrim close it without a change and callonCancel. Focus moves into the
dialog and back to the input. Selective Sass builds add"dialog"and
"modal-display". -
Value axes for charts:
.axison a line, column, bar or mixed chart
labels its value scale at round values, with a grid line at each, and
widens an end the chart chose to the next round value. The labels repeat
the cells' currency or percent sign and shorten large numbers. A mixed
chart labels itsendscale on the far side and stretches it so both
label sets share the grid lines. On a radar chart,.axisrings the grid
at round values and labels the rings. -
Non-Latin text faces: Roboto and Noto Sans now ship Latin Extended,
Cyrillic, Cyrillic Extended, Greek, Greek Extended and Vietnamese subsets,
and Noto Sans adds Devanagari, at 400, 500 and 700. Each face has a
unicode-range, so a browser downloads only the scripts a page renders. -
Cut corners:
--md-sys-shape-corner-family(roundorbevel), a
corners="cut"switch that cuts the corners of cards, dialogs, sheets,
menus, fieldsets, buttons, chips, alerts, snackbars, tooltips, popovers and
outlined fields, and a.corner-cututility. Built on CSScorner-shape;
browsers without it keep rounded corners. -
Spring motion tokens from Material 3 Expressive:
--md-sys-motion-spring-{fast,default,slow}-{spatial,effects}with a
-durationtoken each, solved at compile time intolinear()easings.
motion="expressive"on the root or a subtree selects the expressive
spatial springs. The Sassspring($damping, $stiffness)and
spring-duration()functions build custom ones. -
Emphasized type roles from Material 3 Expressive:
.{role}-emphasized
classes for all fifteen roles and
--md-sys-typescale-emphasized-{role}-font-weighttokens, at500for
display, headline, title-large and body and700for title-medium,
title-small and label. Roboto and Noto Sans now ship a700face, fetched
only when a page renders that weight. -
A "Design tokens" documentation page at
/tokens.htmllists every token
family, its prefix and the page that documents it, with an override
example. -
Material 3 motion tokens:
--md-sys-motion-easing-*for the standard,
emphasized and linear curves and--md-sys-motion-duration-short1to
extra-long4. Components now read them wherever they used an on-scale
curve or time, so:root { --md-sys-motion-duration-short4: 150ms; }
speeds up every 200ms transition. Computed values are unchanged. -
Material 3 corner tokens:
--md-sys-shape-corner-nonetofull, with the
Expressivelarge-increased,extra-large-increasedand
extra-extra-largesteps. Component shape tokens on the scale read them,
so:root { --md-sys-shape-corner-medium: 8px; }squares off every medium
corner. Computed values are unchanged. -
Elevation tokens:
--md-sys-elevation-level0tolevel5and
level1-half, generated from the$elevationsmap. Thez-depth()
mixin, the.z-depth-*classes and every component read them, so a theme
can change a shadow level at runtime. Computed values are unchanged. -
--expressive-z-*properties name the stacking order of fixed and floating
surfaces, fromtoolbar(996) toconfetti(10001). Components read them
instead of literalz-indexvalues, and the Shadow page lists them. -
Layout helpers take a window-size prefix:
.medium:,.expanded:,
.large:and.extra-large:versions of the display, flex direction and
wrap,justify-*,align-*, text alignment,.gap-*, margin and padding
classes, and.hide, apply from that size class up, so
flex flex-column expanded:flex-rowstacks until Expanded. -
More helpers:
.block,.inline-block,.grid,.inline-grid,
.contents,.relative,.sticky-top,.flex-row,.flex-nowrap,
.justify-around,.justify-evenly,.align-baseline,.self-*,
.order-first,.order-last,.shrink-0,.w-full,.h-full,
.max-w-full,.aspect-1x1,.aspect-4x3,.aspect-16x9,
.object-cover,.object-contain,.bordered,.border-top,
.border-bottom,.border-start,.border-end,.corner-noneto
.corner-full,.text-balanceand.text-pretty. -
Print styles. Paper uses the light scheme and hides navigation rails,
navigation bars, the FAB menu, snackbars and tooltips. External links in running text print their address, headings
stay with what follows, and table headers repeat on each page.
.hide-on-printand.print-onlycover the rest. -
.prosestyles a container of bare HTML, such as rendered Markdown: a
70-character measure, heading and list spacing, underlined links in lists
and headings, and a surface for inline code and<pre>. It only touches
elements without a class and sits in the base layer, so components inside
it keep their own look. -
.query-containermakes an element a query container. Horizontal cards
and description lists inside one stack when it is narrower than 600px, so
they adapt to a sidebar or pane on a wide window. -
Contrast levels:
contrast="medium"andcontrast="high"on the root or
a subtree select M3's medium and high contrast schemes. Accents, their
containers,on-surface-variantand outlines move to tones that reach
about 4.5:1 and 7:1 against the surface.prefers-contrast: moreapplies
the high scheme unless acontrastattribute is set, and
contrast="standard"opts out. -
Color swatches component: a
fieldset.color-swatchesof radios in
labels with visually hidden names, each drawn as a round chip of the color
set in--md-comp-color-swatch-color, with a ring around the checked one.
An<input type="color">at the end adds a custom value. CSS only. -
Number stepper: two trailing
.icon-button.suffixbuttons with
data-step="down"anddata-step="up"in a.fieldstep its
<input type="number">withinmin,maxandstep, and fireinput
andchange. The browser's spin buttons are hidden. The Forms behavior
handles it, so there is nothing to initialize. -
Color picker component:
ColorPickerenhances a.color-pickeraround an
<input type="color">with a saturation and brightness area, the
framework's slider as a hue control, and a hex field. The native input stays
the form value and firesinputandchange. The area is a keyboard
slider with a spoken value. AutoInit starts it. -
A "Frosted glass" documentation page at
/glass.htmlcovers the
--expressive-glass,-blurand-opacityproperties, the Sass defaults,
the surfaces that frost, and the reduced-transparency fallback, with live
demos. A button in the docs app bar turns glass on across the site. -
A navigation rail destination can open a
.navigation-rail-panelside
panel of more links. Make the destination a<button popovertarget>and
put the panel popover right after it. The panel docks beside the rail at
full height, a click toggles it, and with a mouse hovering the destination
opens it.<details>groups inside the panel are its dropdowns. -
.org-chartdraws a reporting line top down, with each person as a card
under their manager. Write nested lists; a manager's card is the
<summary>of a<details>that folds the team away. Cards take an
.org-chart-roleband, an.avatarand an.org-chart-name. A wide
chart scrolls sideways. CSS only. -
The
expressivecss-migrationagent skill moves a project from Materialize
or an older 0.x release to current names in eight steps: package and
assets, JavaScript API, removed components, classes and markup, the aliases
0.16.0 removes, silent visual changes, and verification. Each step ends with a
search for the names it replaces, and the skill installs on its own with
npx skills add BaezFJ/ExpressiveCSS. -
An "AI agents" documentation page at
/ai.htmllists the plain-text docs,
the install command for the agent skills, the MCP server configuration and
theexpressivecss-lintcommand. Its skills table is read from each
skills/<name>/SKILL.md, so a new skill appears without editing the page. -
Menubar component: a
.menubarof.menu-triggerbuttons gets the menubar
and menuitem roles, one Tab stop, Left, Right, Home and End along the bar,
and Left and Right between open menus. Each item opens an ordinary Menu.
AutoInit starts it. -
Mega menu component: a
nav.mega-menuof links and buttons whose
.mega-menu-panelpopovers open wide panels of grouped links below the bar.
CSS only, on native popovers. -
Tour component: a
.tourmanual popover walks through<section>steps
beside eachdata-tour-target, with Back, Next, Done, progress text,
skipped missing targets, Escape and focus return.Expressive.Touradds
start(),go()andend(). -
Image compare component: a
.image-comparereveals its second image up to
a native range input's value. TheImageComparebehavior moves the
divider, and Slider no longer initializes that range. -
Calendar component: a page-rendered
table.calendardraws a month grid
with weekday headers, today, outside days and event chips. CSS only. -
Sortable lists: a
.sortablelist reorders by dragging an item's
.drag-handleor with its[data-move]buttons, announces each move and
fireschangewith{ item, from, to }. Semantics require the buttons. -
<input type="color">is drawn as a round swatch of its color. -
Description list component:
dl.description-listlays out label and value
pairs in two columns with a divider per pair, stacking each label above its
value at Compact widths. CSS only. -
Error summary: an
.error-summaryalert in anovalidateform stops an
invalid submit, lists one link per invalid field with its label and the
browser's message, and takes focus. Each link focuses its field. Part of
the Forms behavior. -
Resizable panes: a
<button class="drag-handle">between the two panes of
a list-detail or supporting layout resizes them by drag, arrow keys, Home
and End, or a click that steps through narrow, middle and wide. The
PaneResizebehavior writes--md-comp-pane-list-widthor
--md-comp-pane-supporting-width. -
Data table bulk actions and row expansion: a
.data-table-actionsbar
joins the table's top edge and takes the selected color while rows are
selected, and atr.data-table-detailshows while the row before it holds
a button witharia-expanded="true". CSS only; the page flips the state. -
Alert component:
.alertis a persistent message in the page flow with an
optional leading icon, a heading and text, an.alert-actionsrow and a
close button.criticalswitches to the error container colors. CSS only.
It replaces the banner as the answer for persistent, nonblocking messages,
and the banner migration page now points to it. -
<meter>gets an 8px bar on asecondary-containertrack. The fill is
primaryin the optimum region,tertiaryone region away anderrortwo
away, from the nativelow,highandoptimumattributes. No class is
needed, so existing meters change appearance and fill their container's
width; setwidthon a meter to keep it inline. -
Copy buttons:
data-copy-for="id"copies a field's value or an element's
text, anddata-copy="text"copies the text itself. A snackbar confirms the
result.Expressive.Copy.i18nanddata-copy-message/data-copy-error
replace its texts. -
Theme switch:
data-theme-set="light|dark|auto"on any control writes the
themeattribute, saves the choice tolocalStorageand marks the current
control witharia-pressedoraria-checked.Expressive.Themeadds
set(),get()andsync(). Restoring a saved choice before first paint
stays with the page's inline script.
Fixed
-
Nonsearch TreeSelect opening now honors Menu
autoFocus: falseand preserves
focus already moved to a tree row before its opening animation completes. -
Searchable tree rows inherit the shared menu tokens, preserving visible
keyboard focus, selected colors and row spacing in light and dark themes. -
Search popups resize after filtering so translated no-results feedback and
newly exposed tree paths fit, including inside transformed containers. -
Search popups stay hidden for controls initialized while disabled. Enabling
the control and callingrefresh()restores normal interaction. -
Searchable selects preserve focus on a result reached before the opening
animation finishes, so Enter still chooses that result in dialogs and shadow roots. -
Escape dismisses an anchored menu while its opening animation still leaves
focus on the trigger, including a searchable select reopened from the keyboard.
A closed menu leaves Escape available to its surrounding dialog or page. -
Navbar form inputs and card media captions use their complete body-large
and title-medium type roles, including letter-spacing. Existing captions
now use title-medium tracking, which can change their text width or wrapping. -
The Timepicker canvas fade reads
--md-sys-motion-duration-short3, changing
its default from 175ms to 150ms. Expanded card content reads the new
--md-comp-expanding-card-content-delayproperty, backed by
--md-sys-motion-duration-short4, changing its default delay from 180ms to
200ms. Both follow token overrides and keep their reduced-motion resets.
Timepicker's JavaScript timers anddurationoption are unchanged. -
The map popup's shadow reads
--md-sys-elevation-level2, so a theme
that changes that token changes the popup too. The shadow was written
out in full, so the popup kept the default level 2 shadow while menus
and cards followed the theme. It still overrides MapLibre's own shadow. -
Chips grow with the browser's font size. Labels read the label-large
font-size token, and the icon, minimum height, avatar, close-button box
and editable input use rem sizes. At a 32px root, the default label is
28px and minimum height is 64px. Default rendering at a 16px root,
px spacing and existing custom-property overrides remain unchanged. -
Icons grow with the browser's font size in dialogs, snackbars, alerts,
lists, trees, timelines, the command palette, message avatars, the map
popup, fieldset legends, the drop zone and the dialog, side sheet and
pane header actions. Their sizes were in px, so the text beside
them grew and the icons didn't. Icon boxes in the same rules grow with
them, and the timeline text and the map popup text clear the larger
badge and close button. A short timeline event stays tall enough that
its badge doesn't overlap the next one. The Timepicker's tick text grows too; its dial
and ticks stay in px because the script places ticks in px. At the
default 16px size nothing moves. -
A single-line list row sits in the middle of the row. The row grid's
empty second line stretched, so the text sat 4px above its leading icon
in most rows. A checkbox or radio label row also took the control's
12px row gap and sat 7px high. In a row taller than its text, the
headline and its supporting text now sit together in the middle instead
of being pushed apart. A checked checkbox in a label row shows its
checkmark on the box, not 16px before it. -
A table cell with the
hiddenattribute is hidden. The globaltd, th
rule setdisplay: table-cell, which overrode the browser's
[hidden]rule. -
Security: Autocomplete with
allowUnsafeHTMLrenders only a script-supplied
textas HTML. Aselectedid that has no data entry, which
data-autocomplete-optionscan set, used to become HTML too, so sanitized
user markup that kept the component's class and options attribute could
run script once the menu showed no results. -
Security: a questionnaire with
data-saveno longer stores password or
hidden inputs. It used to keep a password inlocalStorageas plain text
until submit, and to restore both into the next visitor's form, which
replaced the server's hidden values. Saved progress from an earlier
version no longer matches and starts fresh. -
A docked date or time picker, and a tooltip placed without CSS anchor
positioning, stay beside their trigger on a page scrolled sideways and
inside the body's margin. They used to be kept inside the body's box,
which scrolls with the page, so a picker that fit the viewport could jump
to its right edge. They are now kept inside the viewport only, and a
docked picker with analignother thanleft,centerorrightis
kept in view like a left-aligned one. -
@use "expressive" with ($text-color: …)colors the page text. The
variable was written onhtml, where thebodyrule's own on-surface
color covered it, so it only took effect inside a shadow root.body
and:hostnow take their color from$text-color. The default is
unchanged. -
MCP static inspection checks the
require-idrefand
require-owned-descendantrules fromsemantics.json. The server kept
its own copy of the rule helpers, which had drifted and skipped both, so
markup that passed before can now get findings. It now imports the same
rule module the repo's semantics checks use. -
Timepicker reads AM or PM wherever it appears in a value, so
02:30:45 PMopens at 2:30 PM. The period was looked for only after the
first colon, so such a value, or one like2 PM, opened as AM. -
With a query typed, the first Escape in the command palette clears it and
the next closes the palette, in every engine. Chromium and Firefox already
did this for a search input; WebKit closed at once, and so did every
engine for a text input. An Escape that cancels an IME composition is left
to the IME. -
In forced colors, a filled keypad key such as Call has a 3px edge. The
fill that set it apart is dropped there, so it looked like every digit. -
In forced colors, a determinate circular progress draws its track as a
thin line through the ring, so the value and the track differ in shape.
Both were full-width rings, told apart only by GrayText against
CanvasText, which Night sky sets close together. -
A narrow heatmap shows whole column labels. Like the line, column and
mixed charts, it keeps every second label (or every third, and so on)
when each is wider than its column, instead of cutting all of them to
"W…". Labels the author already spaced out with emptydata-label
cells, such as a year of weeks named once a month, are left as written.
The labels are now measured where they sit, so the columns' gaps and
right-to-left grids are counted. -
Pie slices 5 and 6 stand out from the surface. They took
primary-containerandtertiary-container, which are 1.3:1 on the
surface in light mode and 2:1 in dark, under the 3:1 a slice needs. Each is
now its container role mixed 45% withon-surface: the same hue, at 5:1
in light and 6.8:1 in dark..valuestext on them issurface. A page
that sets--md-comp-pie-chart-color-5or-6keeps its own colors. -
In forced colors, an invalid checkbox has a dashed edge. Forced colors
drop the error color, so it looked like any other box. A checked invalid
box used to paint the author's error red and on-error mark, because the
error rules outranked the forced-colors ones; it now takes Highlight and
HighlightText like any checked box, and a disabled one stays GrayText. -
Chart cards in the docs keep their contents off the border, and the
period switch sits beside the stat instead of on a row of its own. The
column chart page now says a card needsp-4around its stat and chart,
as the line chart page already did. -
A narrow line, column or mixed chart shows whole row labels. When each
label is wider than its column, the chart keeps every second label (or
every third, and so on) and hides the rest, rather than cutting all of
them to "J…". Twelve months on a 320px phone show as six. The chart
re-checks as it resizes, and the full text stays in the tooltip and the
table. -
A top app bar whose destinations do not fit beside the title wraps them
onto a second row. Before, the title shrank to one letter and an
ellipsis. On one row the bar is unchanged. -
In WebKit, the keyboard can scroll a docs code sample that is wider than
its block. Chromium and Firefox already put such a block in the Tab
order; WebKit skipped it. -
The Carousel page shows the Show all action it recommends, opening a
full-screen dialog of the same items. It is the single-pointer way
through a carousel (WCAG 2.5.7), and no demo had it. -
The Enter that confirms an IME candidate no longer acts on a field. In
the date picker it moved focus into the calendar and read the unfinished
text; the time picker, the autocomplete's active option and the color
picker's hex field also took it. The date picker, time picker and
autocomplete now skip every key of a composition, so the arrows and
Escape used to pick a candidate leave them alone too.
Utils.isComposing(e)checksisComposingand Safari's keyCode 229, and
Chips, Command palette, Menu and Questionnaire now share it, so Menu and
Questionnaire also skip Safari's confirming Enter. -
A keyboard-focused slider handle takes the shared 3px focus ring. Its only
cue was a 10% tint around the handle, about 1.1:1 against the surface. -
In a browser without CSS anchor positioning, a popover is centered in the
viewport. It used to land in the top-left corner, far from its button. -
A scrolling marquee no longer restyles every item on each frame. The
reset'sbox-sizing: inherittied each item's style to its moving group,
so on a page that also drew every frame, each frame restyled every item
as well as the groups. Items now setbox-sizing: border-boxthemselves. -
A dot map marker takes taps in a 48dp circle around its 16dp dot. It was
a 16px target on a map that is itself a target, under WCAG 2.5.8's 24px
minimum. -
A snackbar with
displayLength: Infinitygets its close button by
default, as one with an action does. Without an action it could otherwise
only be swiped away. An explicitdismissible: falseis kept. -
A
nowrapbreadcrumb scrolls sideways as documented. Its crumbs used to
shrink below their text, so each separator sat on the last letter of the
crumb before it. -
A list row's trailing
span.badgeorspan.metastays in the last
column. The label rule matched it and outranked the trailing rule, so a
bare-text label like "Inbox" was pushed after the badge. -
Controls grow with their text. Type is set in
rem, but buttons, icon
buttons, FABs, badges, avatars, keypad keys, text fields, selects, the
app bar, the search bar and the slider's inset icon usedpxboxes. With
a larger browser font size or 200% text-only zoom, their labels and icons
spilled out or were cut, and a field's floating label sat on its value.
Those sizes are nowrem(a newrem()Sass function), so pages render
the same at the default 16px. A field's clearance for its prefix, suffix,
password toggle and number stepper grows with them. -
Charts, cards, stats, KPIs and the org chart grow with their text too:
- the chart height, y-axis width, bar and heatmap row heights, pie label
room, radar label room and tooltip width; - the small, medium and large card heights;
- the stat tile minimum;
- the KPI icon box;
- the org chart card width.
At 200% text, axis labels no longer shrink to "8…" or "$…", a pie or
radial total stays inside its ring, and stat tiles wrap instead of
breaking "$48.2k".
- the chart height, y-axis width, bar and heatmap row heights, pie label
-
A trigger with both a Tooltip and a Menu keeps both anchors. Each wrote
the wholeanchor-nameproperty, so the second replaced the first, and
destroying either removed the other's. Tour targets share the same list. -
Timepicker on an
<input type="time">writes 24-hourHH:mm. A
12-hour picker wrote02:15 PM, which a time input rejects by emptying
itself. -
Tab closes an open Lightbox. The image zooms in place rather than in a
modal, so Tab used to move focus to the page behind the overlay. -
The data table page's sort script compares numeric columns as numbers
and readsdata-valuefirst, so$1,200.00sorts after$87.00. -
Forced colors (Windows contrast themes) paint every fill as Canvas, and a
few controls drew nothing else:- Filled, tonal and elevated buttons, filled and tonal icon buttons,
keypad keys, the keyboard, the questionnaire's footer buttons, the bar
chart track and the search bar now get an outline. - A pressed toggle takes the Highlight fill.
- The select caret and check, the submenu chevron and the bottom sheet
handle, all glyphs masked from a fill, paint in CanvasText. - Slider stops come back, and the inactive track is CanvasText, so
GrayText now means only disabled. - Disabled checkboxes, radios and their labels turn GrayText.
- FABs (
.extra,.extend,.large.circle) and.buttonget the
outline too, the column chart track is outlined like the bar chart's,
the color picker area gets an edge, and every aura variant, rainbow and
glow included, is the solid Highlight ring its docs describe.
- Filled, tonal and elevated buttons, filled and tonal icon buttons,
-
Blockquotes, table cells and the stacked
.responsive-tableheader use
logical properties, so the quote bar, cell alignment and header border
mirror in a right-to-left page. -
The lightbox opens above a non-modal bottom or side sheet. Its overlay and
zoomed image were atz-index: 1000, under the sheets' 1100; they now use
--expressive-z-lightbox(1200). -
Swipeable tabs no longer jump back to the previous panel when you pick a
new one while the last scroll is still running. The track released its
target as soon as its position rounded to the new panel, so the rest of
the old scroll selected the panel it was heading to and firedonShow
for it. The target now holds until the track arrives. -
Static chip close buttons work again after
AutoInit(). The fallback
handler stepped aside for any.chipswith an instance, and an instance
withoutallowUserInputnever handled the click. Chips also copy their
initialdata, ignore Enter during IME composition, and mirror arrow keys
in right-to-left text. -
Menu clears its old highlight when it closes or moves, so reopening no
longer leaves two rows active. A.menu-triggerwhose target id does not
resolve now warns and stays inert instead of inserting the text "null".
Menubar arrow keys skip disabled items. -
A multi-select's checkbox stays checked when the box itself is clicked, and
a disabled option in a single select no longer closes the menu. -
Autocomplete keeps the active row when a multi-select option is toggled and
doesn't search again. It returns focus to the field after a single-select
click, and takes Escape only when it closes the list, so an enclosing dialog
stays open. Results set before the field is focused don't open the list.
destroy()removes the combobox attributes it added. -
Datepicker sorts multiple dates the same way in every browser, puts the tab
stop on today when nothing is selected, keeps focus on the year or month
select across a redraw, and no longer changes the caller'sminDateand
maxDate. A typed date in multiple-selection mode joins the selection.
destroy()removes the date input's painted value. -
Tabs find panels whose ids contain non-ASCII characters or sit in a shadow
root, and a shown panel keeps its CSSdisplayinstead of becoming
block. -
Carousel leaves Alt, Ctrl, Meta and Shift shortcuts alone, as well as arrow
keys aimed at sliders, menus, tab lists and media controls inside a slide. -
A snackbar swipe made within one millisecond no longer counts as a flick.
-
A textarea hidden at load (in a closed dialog or an inactive tab) gets its
height when it is first focused or typed in instead of staying 0px tall. A
.file-fieldwithout a path input no longer throws. -
Tour skips steps whose target has been removed. A side sheet starts a drag
only from its own header. Escape during a sortable drag no longer closes
the dialog around the list. -
Lightbox returns focus to the image when it closes and doesn't open an
image it can't measure. Particles uses the latest visibility entry,
repaints still dots after a theme change, and follows reduced motion as it
changes. MapView skipsdata-lng-latvalues with an empty part and
handles cluster zooms and fullscreen requests that finish after
destroy(). -
A keypad initialized before it is in the document works once inserted. The
slider's value label follows its host when the host resizes. -
RichTextEditor logs invalid toolbar
data-attributesJSON instead of
throwing. A required editor blocks submit while empty and takes focus,
where the hidden textarea used to block it with no message. -
The character counter shows a prefilled value's count and invalid state
without waiting for focus. ExpandingCard callsonClosewhen its dialog is
closed natively. Card reveals and the app bar's search view work inside a
shadow root. ExpandingCard, AppBar, the FAB menu and CharacterCounter undo
their ARIA and tabindex changes ondestroy(), and an<a>FAB trigger
withouthrefis focusable. -
A navigation rail
<button>destination is now as tall as a link
destination instead of the default button's 40px. -
A side panel destination that comes first in a navigation rail is laid out
as a destination, andNavigationRailno longer treats it as the menu
toggle. -
The navigation rail side panel keeps its 12px padding. The global
[popover]reset in the menu styles had cleared it. -
A modal or compact NavigationRail no longer collapses when blank space
inside it is tapped. Its scrim is the rail's own::before, so those taps
looked like scrim taps; only a tap beside the rail collapses it now. -
Touch drags on a bottom sheet's
.drag-handleand a side sheet's
<header>now dismiss the sheet. Withouttouch-actionthe browser took
the drag as a pan and cancelled the pointer, so the sheet snapped back. The
bottom sheet's built-in handle band and the side sheet's inner edge have no
element of their own to carrytouch-action, so on touch screens the
explicit handle and the header are the drag areas. -
A docked Datepicker for a date range now opens from its end input, and a
click on that input no longer closes it. The plugin counted the end input
as outside. -
Menus listen for outside clicks on the document instead of
<body>, so a
click below the content of a page shorter than the window closes them. -
A MapView marker or popup whose
data-lng-latis not "longitude,latitude",
such as Google's latitude-first order, is skipped with a warning. MapLibre
threw on it after the map was created, which leaked the map and stopped
the rest of thatinit()call. -
Restarting a running Tour releases the step it was showing, so only one
target carries the highlight. -
Autocomplete results that arrive after the user left the field update the
list without opening it under the input. -
A date typed into a Datepicker that the calendar would not offer, outside
minDate/maxDate, a disabled weekend or refused bydisableDayFn, now
puts the input back to the selected date. A date beforeminDateused to
becomeminDatein the picker while the input, and the submitted form
value, kept the typed text. -
A Datepicker input's initial value is now the selection, as it already was
once the input was clicked.instance.datewas undefined until then, and a
multiple selection started empty. -
A form reset now resets a Datepicker: the inputs added for extra dates in
multiple selection are removed, and the selection is read back from the
reset values. A refused date typed into one of those inputs restores that
input's own date. A Slider's fill and value label also follow a form reset. -
Skipping a Questionnaire question clears its error. A skipped question
whose answer had been wrong still showed the error when the user went back. -
A tooltip opened by hover still closes on Escape but no longer takes the key
from the focused control, so the same Escape also closes the dialog or menu
the user is in. On its own trigger, Escape stays the tooltip's. -
A hover menu no longer moves focus into itself when the pointer passes its
trigger, which pulled focus out of the field being typed in. Space, Enter or
an arrow key on the trigger moves focus into the open menu, and Escape
closes it from wherever focus is. -
Clicking inside an open Autocomplete input, for example to move the caret,
no longer closes its list. A trigger's own click is no longer treated as an
outside click by Menu. -
Enter in a Timepicker hour or minute field writes the typed time, after an
emptied field takes its default as it would on leaving. With the
defaultautoSubmitthere is no Ok button, so a keyboard user could type a
time but never set it. -
Chips selection follows focus. Arrow keys used to move from the highlighted
chip while Backspace deleted the focused one, and the highlight stayed after
focus left the field. -
Behaviors that listen on the document now work inside an open shadow root.
Copy, Theme, Dialogs light dismiss, bottom and side sheet dragging, the
bottom sheet handle button, ImageCompare, PaneResize, Sortable, Tour
controls, the password toggle, error summary links and drop zones read the
event'scomposedPath()instead of its target, which the document sees as
the shadow host. Sortable keeps focus on a moved item's button there, Theme
marks the controls in the clicked control's shadow root, and a sheet closed
there in the middle of a drag reopens without the drag offset.
Menubar moves between open menus, ScrollSpy marks links, and a card reveal
trigger named byaria-labelledbyis accepted in a shadow root too. A
tooltip under a positioned shadow host now sits next to its trigger instead
of offset by the host's position.change,submit,resetandtoggle
events do not leave a shadow root, so file lists, field validation, the
error summary and Tour's own toggle handling still need light DOM. -
Focused read-only text fields now show the focus underline. The field's
focus styles skipped[readonly], and FormSelect makes its combobox input
read-only, so a focused select had no visible focus indicator. -
@use "expressive" with ($md-source: …)now sets the theme seed, as the
changelog for 0.13.0 and the migration skill describe. The variable moved
toabstracts/_variables.scss; before, Sass rejected the configuration
because the tokens layer is loaded withmeta.load-css(). -
The time picker's clock face is sized from
dialRadius. It was a fixed
260px while the hand and numbers centred on the default 135px radius, so
the hand sat 5px off centre, further with a customdialRadius. -
Menus, context menus and select dropdowns open next to their trigger in
right-to-left pages. The menu kept the browser's[popover]inset: 0,
so with its explicit width the right edge won overleft, and the menu
opened against the right side of its container. -
Unlayered CSS can set a shadow on any component again. Thirteen component
rules that remove a shadow (text buttons, disabled buttons, docked
toolbars, filled and outlined cards, full-screen dialogs, separated panes,
the search view's bar) usedbox-shadow: none !important, and a layered
!importantbeats every unlayered rule. Only the.z-depth-0utility
keeps the flag. A focused disableda.buttonwith anhrefno longer
shows the enabled fill, a disabled FAB takes the disabled fill instead of
the primary container, and anelevatedpane in a separated or floating
layout now shows its elevation. Disabled FAB menu items and toolbar FABs,
includingaria-disabled="true"ones, paint as disabled and no longer lift
or tint on focus;aria-disabledones still take hover, so a tooltip can
say why..hoverableno longer lifts a disabled element. -
A shadow root that adopts the sheet as its only stylesheet now inherits the
page defaults:border-boxsizing, the body-medium type role and the
on-surface text color. These were set only onhtmlandbody, which a
shadow tree cannot reach, so its elements werecontent-boxwith the
browser's default text color. -
With reduced motion requested, the floating label, switch handle, icon
button shape, slider value label, chips, time picker dial and the open card
reveal panel no longer animate. -
A carousel that reaches the end of its track keeps the item you moved to.
Items past the end share that scroll position, and a one-pixel rounding
difference in WebKit could select a later one in a right-to-left carousel. -
Selection and checked states stay visible with forced colors (Windows High
Contrast). Tabs, navigation bar and rail destinations, pagination, toolbar
actions, menu and list items, mega menu links and filter chips mark the
selected item with a Highlight outline. Checkboxes, radios and switches draw
their checked state in system colors and get an outline on keyboard focus.
The datepicker marks today, the selected day and the range. Small badges,
the loading indicator and carousel dots keep their shape. A keyboard-focused
tab also gets the focus ring instead of only a faint tint. -
Right-to-left pages lay out text fields, checkboxes and more on the correct
side. The floating label, prefix and suffix icons, trailing icon button,
character counter and the select caret's inset follow the reading
direction, as do the checkbox mark, the date range band, the modal
navigation rail, the snackbar container and its padding, the dialog header
form, menu item text and the nested menu's entry slide, and the map popup's
close button. A field's label, icons and icon clearance follow the field's
dir, so an input withdir="auto"or its owndirkeeps its text clear
of the icon. Fields and the character counter readdir; a page that sets
only the CSSdirectionproperty renders them left to right. Known issue:
the date picker'sisRTLoption reverses the cells in script, so the range
band points the wrong way with it on, and on an RTL page it turns the
calendar back to left to right. On an RTL page, leaveisRTLoff. -
The modular entry now exports the option types of
CommandPalette,
Confetti,Particles,Keypad,MessageScroller,RichTextEditorand
MapView, as it already did for the other components. -
The docs now cover the styled native
<meter>on the progress page, the
onOpenandonClosecallbacks of cards and expanding cards, the
timepicker'sdialRadius,outerRadius,innerRadiusandtickRadius
options,PaneResize.width()andPaneResize.setWidth(),
Copy.textFor(), and the.overlinehelper. -
A JavaScript tooltip whose trigger sits in a shadow root now starts hidden
and absolutely positioned. The tooltip goes into that shadow root, where
neitherbody > .tooltipnor:not(body) > .tooltipmatched it, so it
showed in the page flow before it was ever opened.:host > .tooltipnow
gets the same rule asbody > .tooltip. -
A tap on a menu item after scrolling the menu by touch closes the menu
again. The scroll flag was cleared only by a click, and a touch scroll
ends without one, so the next tap was ignored as part of the scroll. -
Datepicker's previous and next month buttons have accessible names. The
previousMonthandnextMonthi18n strings were never read; they now label
the buttons and default to'Previous month'and'Next month'. -
A drag that starts on a bottom sheet's
.drag-handlenow moves the sheet
wherever the handle sits. The drag check looked only for the pre-1.0
.handleclass.
Removed
- The version resolver's JSON output (
scripts/resolve-version.mjsin the
skill and the MCP package) dropsdeclaredVersion,
skillContractVersionandmatchingCommit. The first two copied
declaredRangeandcontractVersion, which remain;matchingCommit
was alwaysnull. ReaddeclaredRangeandcontractVersioninstead. - Datepicker's undocumented
formatsproperty, the table of format
functions. Theformatoption and its tokens are unchanged. - Breaking (0.16.0). Most names kept for markup written before 0.8.0 are
gone. Each one has a replacement that has been documented since 0.8.0;
see Migration below. A few stay:.breadcrumband.breadcrumb-wrapper,
a bareul.pagination, and checkboxinput + span, which multiple Select
still emits. - Breaking (0.16.0). The remaining compatibility paths are gone too: the
.rangeand.range-fieldslider hosts, a switch whose.switchclass
sits on a wrapper around the label, the.extrasize on a common button,
radioinput + spanand switchinput + span/.leverdrawing, the
li.tabwrapper, a chip's.active, Carousel's.flatlayout,fullWidth
option and.carousel-fixed-item, and thebackground,on-background,
surface-variantandsurface-tintcolor roles with their-light/-dark
pairs and utility classes. - Selectors and code that never matched or never ran. The date and time
picker rules no longer carry hyphenated twins of their generated parts,
such as.date-picker-container; the script only ever emitted the
unhyphenated names. The root.date-pickerand.time-pickerclasses
still start the components. Also gone: the.tabbedfocus class, which
every rule already paired with:focus-visible; the.halfway-fab,
.searchbar.has-sidebarand.prefix ~ .chipsoffsets; the
picker footer rules for buttons the script never created; and the
normalize rules forh1,b,strong,smallandkbd, which the
typography rules already override. - Datepicker's
startRangeandendRangeoptions. Nothing set them, and the
is-startrange,is-endrangeandis-inrangeclasses they produced had
no styles. UseisDateRangefor range selection.
Changed
-
The
expressivecssskill is version 0.12.0 andexpressivecss-reviewis
version 0.1.1. A review now leaves the reviewed files unchanged and only
offers its fixes.expressivecss-migrationships at 0.1.0. -
Chips' form-associated control moved to a shared core module, which the
cascader uses too. The control gainedcheckValidity()and
reportValidity(). -
An open menu is a popover in the browser's top layer. This covers Menu,
Select, Autocomplete, the menubar, Chips suggestions and the date
picker's menus. A container that clips or scrolls no longer cuts a menu
off, a modal dialog or sheet no longer covers it, and it draws above
everyz-index, so--expressive-z-menunow only orders the docked date
and time pickers. Menu no longer setsposition: relativeon the
nearest clipping ancestor. The menu'spopoverattribute ismanual,
and Menu still handles outside clicks and Escape itself. A context menu
is kept inside the window rather than its scrolling container, and stays
where it opened when that container scrolls. AcoverTrigger: true
menu, or a menu whose trigger sits under a transformed ancestor, is
placed by measurement and follows its trigger when the page or a
container scrolls. Firefox would anchor a top-layer menu to the
trigger's box before transforms, which puts it beside a floating toolbar
centred withtranslateX(-50%)instead of under its button. Migration:
tests that run Menu in jsdom needshowPopoverandhidePopoverstubs,
and CSS or script that read an open menu's inlinedisplay: block
should usemenu[id]:popover-openor the instance'sisOpen. -
An open Lightbox shows its overlay, image and caption as manual popovers
in the browser's top layer. The image still zooms from its thumbnail
and back, and Tab, Escape, a click and scrolling still close it; focus is
not trapped. Lightbox no longer setsoverflow: visibleon every
ancestor of the image while it is open, which let a scrolling container
lose its clipping.--expressive-z-lightboxno longer applies to it. The
image carriespopover="manual"only while open. Migration: tests that
run Lightbox in jsdom needshowPopoverandhidePopoverstubs. -
The
.mx-*,.my-*,.px-*and.py-*helpers setmargin-inline,
margin-block,padding-inlineandpadding-block. Horizontal text
renders as before. In a vertical writing mode,.mx-*and.px-*now
space the top and bottom, following the text, and.my-*and.py-*
the left and right. -
Menu, navigation bar and slider state layers read the
--md-sys-state-*-state-layer-opacitytokens instead of fixed 8% and
10% mixes. The defaults render the same. A page that overrides a state
token now changes these layers too. -
Every component inherits one
Component.init, and the base constructor
mergesdefaultswith the options passed in.inittypes are
unchanged. TheCommandPaletteandQuestionnaireconstructors accept
anyHTMLElement, which theirinitalready took, and a subclass that
overridesdefaultsnow gets its own defaults merged. -
Every component inherits one
getInstancefromComponent, which also
sets and clears theel.Expressive_<Name>property. The types are
unchanged, except thatSliderandCharacterCounteraccept any
HTMLElement. A subclass that overridesgetInstancewith its own return
type no longer compiles (TS2417); delete the override, because the inherited
one already returns the subclass. -
The bundle uses
ResizeObserverandIntersectionObserverwithout
checking that they exist. Every supported browser has both, but tests that
run components in jsdom need stubs for them, as they already need one for
matchMedia. -
A click in a Select sets
selectedon the option clicked and leaves the
others alone. It used to write every option. -
Data tables and line charts inside a shadow root sort and format numbers
in the host'slang, as the date and time pickers already do. -
The MCP package ships
scripts/bounded-file.mjsand
scripts/semantics-rules.mjs. Inspection read errors use new wording,
such as "inspected file exceeds N bytes". -
A runtime tooltip that opens while a tooltip is closing, or within 300 ms
after that one'sexitDelayends, shows without waiting its
enterDelay, so moving along a toolbar shows each tooltip at once. Only
pages that setenterDelayabove0see a difference. The first tooltip
still waits its delay, and a tooltip that stays open does not shorten the
others'. The window is fixed and has no option. -
A Chips field whose text
<input>has anamenow submits its chips
under that name, oneideach, instead of the text being typed. A page
that read the draft text from the form should readchips.getData(), or
take thenameoff the input to keep the old behavior. The input now sits
inside<expressive-chips-control>, so.chips > inputselectors and
input.parentElementcode need updating. The input'svalidity,
validationMessage,:invalidandinvalidevent no longer report the
required state; read them from the control or the form.form.elements
lists the control under the field's name. In a Questionnaire step, or on
a.validateinput, an empty required chips field does not count as
invalid. -
Datepicker and Timepicker follow the page's language. With a
langon
the input or around it, the calendar takes that language's month and
weekday names fromIntland its first day of the week where the browser
knows it, and the clock defaults to 12- or 24-hour as that language does.
Options the page passes still win, and the button words stay English
unless passed. A page with nolangis unchanged. To keep the English
calendar on a page with anotherlang, pass the English names ini18n.
A typed date in the picker's ownformatis now read with its names, so
the localized value it writes reads back, and05/10/2026in
dd/mm/yyyyis 5 October rather than whatDate.parseguessed. -
A full-screen
dialog.search-viewopens on a click, Enter or ArrowDown
in the app bar's search field, not on focus. Tabbing through the bar used
to open the modal each time, so every keyboard pass needed an Escape
(WCAG 3.2.1). A docked view still opens on focus. The bar shows the
shared focus ring while its field is focused, since its input clears its own. -
Snackbars queue. A new snackbar waits until the one on screen is dismissed,
then shows, as Material asks, instead of replacing it at once. Migration:
passreplace: truewhere a snackbar should still take over immediately,
as copy buttons now do. A queued snackbar firesexpressive:openand
captures focus when it appears,dismissAll()clears the queue too, and a
snackbar built fromsnackbarIdhas noeluntil it shows. A persistent
snackbar whose container a re-render removed no longer blocks the next. -
Time picker hours step round the day: in 12-hour time, Up from 11 AM
gives 12 PM and Down from 12 PM gives 11 AM, so AM and PM follow the
hour. -
Radar chart gaps: a missing value breaks the outline instead of joining
the spokes either side, as a line chart's does. With.area, each
unbroken run fills as a wedge from the middle, drawn by a new
.radar-chart-areapath, and a value between two gaps is a dot. -
A sheet drag that moves against the dismissing direction now counts as
a drag, so the click that ends it no longer activates a handle button. -
The time picker's hour and minute fields are spin buttons
(role="spinbutton"with their range). Up and Down step them by one and
Page Up and Page Down by five, wrapping round the dial, and the clock hand
follows. Code that found them by thetextboxrole needsspinbutton. -
The
Tooltipplugin anchors its bubble with CSS anchor positioning where
the browser supports it: the tooltip stays beside its activator through
page and container scrolling and flips at the viewport edge. Anchored
tooltips fade in without sliding, andtransitionMovementjoins the gap.
Other browsers keep the measured position. -
Menus opened from a trigger, including Select, Autocomplete, the menubar
and the date picker's menus, anchor with CSS anchor positioning where
supported: they follow the trigger through scrolling, flip at the viewport
edge and scroll when taller than the room on that side. Context menus and
coverTrigger: truekeep the measured placement. -
The tabs indicator moves on the default spatial spring
(--md-comp-tabs-indicator-easingand-duration), and now stops under
reduced motion. TheTabsdurationoption defaults tonull; a number
keeps the old ease-out timing for that instance. Switch handles slide on
the fast spatial spring (--md-comp-switch-motionand-motion-duration),
and the chip set's border and shadow change on the fast effects spring. -
Button groups and the keypad press on the fast spatial spring tokens
instead of the hand-sampled$motion-fast-spatialcurve, through
--md-comp-button-group-motionand--md-comp-keypad-motionwith new
-motion-durationpairs, somotion="expressive"reaches them. The press
settles in 182ms instead of a 200ms window.$motion-fast-spatialstays
for Sass that still names it. -
Reduced motion now reaches every component through the tokens: under
prefers-reduced-motion: reduce, all--md-sys-motion-duration-*and
spring-durationtokens are0s. Lists, the data table, the navigation
bar, the mega menu, checkboxes, radios, pagination, tabs, the
questionnaire and the lightbox had no rule of their own and animated
before. No!important, so a page can opt an element back in. -
Bottom and side sheets slide in and snap back on the default spatial
spring (--md-comp-bottom-sheet-motion,--md-comp-side-sheet-motionand
their-motion-durationtokens) instead of a 280ms ease, so
motion="expressive"applies to them. Each sheet now extends 24px past its
docked edge, with matching padding, so a spring's overshoot never uncovers
the page. -
The navigation rail expands and collapses on the default spatial spring
(--md-comp-nav-rail-motionand-motion-duration). Its width, min-width
and padding no longer follow--md-comp-nav-rail-duration, which now
times only the indicator color. -
FAB menu actions move on the fast spatial spring and fade on the fast
effects spring.--md-comp-fab-menu-easingand-durationnow default to
the spatial spring, and the new--md-comp-fab-menu-effects-easingand
-effects-durationtime the fade. -
Basic dialogs now animate in: they fade on the fast effects spring and grow
from 96% on the default spatial spring (--md-comp-basic-dialog-easing,
-duration,-effects-easingand-effects-duration). They still close
at once. Sheets,dialog.maxand the expanding card's dialog are
unchanged, and reduced motion opens without moving. -
The expanding card morphs on the slow spatial spring:
--md-comp-expanding-card-motion-durationand-motion-easingnow default
to it instead of 500ms on the standard curve. New-content-duration,
-content-easing,-fade-durationand-fade-easingtokens time the close
button and detail content. Close cleanup still follows the actual clip
transition. -
Headings balance their lines and paragraphs avoid a one-word last line
(text-wrap-style: balanceandpretty). The longhand leaves wrapping
itself alone, so.truncateand.text-nowrapstill work. -
A link without a class inside a
p,dd,blockquote,figcaptionor
tdis underlined, so links in running text no longer rely on color alone
(WCAG 1.4.1). Links with a class, and links in lists, menus and navigation,
are unchanged. -
The documentation's category rail opens each group's pages in a side
panel on hover or click, with each section as a dropdown. The section
holding the current page starts open. The panel replaces the page list that
sat beside the rail from Large up. -
Snackbar's generated close button holds its icon in a
<span class="material-symbols">instead of an<i>. Styles that targeted
.snackbar ineed.snackbar .material-symbols. -
Swipeable tabs no longer use Carousel. The panels move into a full-width
.tabs-contentscroll-snap track, so touch and trackpad swipes are native
scrolling. Only the shown panel is reachable by keyboard; the others are
inert. The track re-aligns when its width changes, so a tab bar started
in a closed dialog or sheet shows the current panel once opened. Dragging
the panels with a mouse no longer switches tabs; click the tab instead. -
Components that read
surface-variantnow read
surface-container-highest, as Material 3 maps it: the unselected switch
track, the time picker's clock face and hour and minute inputs, the date
picker's header rule and focused month and year field, and disabled cards.
In dark mode those surfaces are a little darker and more neutral. Rules
that readbackgroundoron-backgroundnow readsurfaceand
on-surface, which resolve to the same colors.
Migration
-
Sass:
@include z-depth("0"),btn-flatandbtn-disabledno longer emit
box-shadow: none !important. A stylesheet that relied on the flag to
cancel its own higher-specificity or inline shadow needs the flag itself.
The.z-depth-0class keeps it. Because the utility layer outranks those
resets,.hoverableon a text button now lifts it on hover, and a pressed
.filledor.outlinedcard with.hoverablekeeps the hover shadow. -
Icons: write
<span class="material-symbols" aria-hidden="true">. A bare
<i>is no longer treated as an icon inside buttons, lists, menus, chips,
bars or fields, and.material-iconsis gone; use.material-symbols
(or the-outlined,-roundedand-sharpvariants). -
Field icons: name the side with
.prefixor.suffix. The positional
i:first-of-type/i:last-of-typefallback is gone. -
Visibility helpers: replace
.hide-on-small-onlyand
.hide-on-small-and-downwith.hide-on-compact-only,.hide-on-med-only
with.hide-on-medium-only,.hide-on-xxl-onlywith
.hide-on-extra-large-only,.show-on-smallwith.show-on-compact, and
.show-on-xxlwith.show-on-extra-large. -
Replace
.no-paddingwith.p-0and.center-blockwith.mx-autoplus a
display: blockof your own..no-selecthas no replacement class; set
user-select: nonein your own CSS. -
Progress: replace
.preloaderwith.progress.circular. -
Date and time pickers: AutoInit starts
.date-pickerand.time-picker
only. Renameclass="datepicker"andclass="timepicker"on the inputs. -
Search: replace
.searchbarwith.search-bar. -
Sheets: replace
dialog.bottomwithdialog.bottom-sheet, and the sheet's
.handlewith.drag-handle. Replace side sheetdialog.rightand
dialog.right-sheetwithdialog.side-sheet, anddialog.leftand
dialog.left-sheetwithdialog.side-sheet.start. -
Toolbar: replace host
.maxwith.dockedand host.filledwith
.vibrant. A.filledaction and a.maxspacer inside the bar are
unchanged. -
Tooltip: replace
.tooltip.maxwith.tooltip.rich. -
Menus: replace a bare
<hr>between items with
<li class="divider" role="separator">. -
File input: the trigger is a
<label class="button">wrapping the
<input type="file">. A<button>or.btntrigger is no longer styled. -
Script: replace
Expressive.RangewithExpressive.Slider. -
Buttons: replace
class="button extra"on a common button with
class="button medium". Its label grows fromlabel-large(14px) to
title-medium(16px).circle extrais still the FAB. A navigation
rail FAB is a plain.button. -
Radios and switches: drop the
<span>or.leverafter the input; the
input draws the control and the label text sits beside it. Put.switch
on the<label>itself:<div class="switch"><label>now draws a
checkbox. -
Sliders: replace a
.rangeor.range-fieldhost with.slider, or wrap
the input in a<label>. -
Tabs: put the
<a>links directly innav.tabs, withoutuland
li.tab. -
Chips: replace
.activewith.selected. -
Carousel: replace
.flatorfullWidth: truewith an M3 layout such as
.heroor.uncontained. A.carousel-fixed-itemchild is now an
ordinary item; position a fixed overlay in your own CSS. -
Color: replace
--md-sys-color-backgroundwithsurface,on-background
withon-surface,surface-variantwithsurface-container-highestand
surface-tintwithprimary, in variables and in the.x/.x-text
utility classes. Overrides of the-light/-darkpairs move the same
way.