Booterial

Material 3 Expressive × Bootstrap 5.3

Bootstrap markup, Material 3 Expressive design.

Keep writing standard Bootstrap HTML. These docs show what each component looks like after the restyle, the extra M3 classes the framework adds, the HTML to copy, and the optional scripts that bring in springs, ripples and the components CSS can't do on its own.

Getting started

Introduction

Booterial is a stylesheet and a few optional scripts that restyle Bootstrap 5.3 to Google's Material 3 Expressive design. Every value is checked against Jetpack Compose's material3 source, Google's reference implementation.

There's no build step: plain CSS custom properties and vanilla JavaScript, so it works from a file on disk, a CDN or any CMS template. The theme comes from one seed color. Every color role is generated in HCT (Material's color space) and each pairing the framework draws is contrast-checked against WCAG before it's written out.

  • Bootstrap markup keeps working. .btn, .form-control, .dropdown, .card and the rest pick up M3 colors, shapes, type and motion automatically.
  • M3-only components get their own classes, such as .btn-fab, .chip, .nav-rail, .loading-indicator and .slider, all documented below.
  • Motion uses springs. The theme solves M3's spring constants into CSS linear() easings, so overshoot and settle come from the physics.
  • The scripts are optional. Everything renders without them; they add what CSS can't do (ripples, sliding indicators, spring momentum, snackbars and so on).

Quick start

Load Bootstrap, then the framework's CSS and your theme. Add the scripts you want after Bootstrap's bundle.

Starter template

Load order matters: m3-bootstrap.css after Bootstrap, then your generated m3-theme.css. In scripts, m3-color.js must come before m3-theme.js, which must come before m3-customizer.js.

Production sites only need m3-bootstrap.css, a theme file and whichever optional scripts you use. The color science, theme generator and customizer (m3-color.js, m3-theme.js, m3-customizer.js) are for building the theme. Export a theme.css from the customizer and ship that.

Files

What each file does and whether you need it.

css/m3-bootstrap.css

Required. Maps M3 tokens onto Bootstrap and adds the M3 components.

css/m3-theme.css

Required. Your theme: color, shape, type and motion tokens. Generate your own with the customizer.

css/m3-shapes.css

Optional. The 35 Material shapes as .md-shape-* clip classes.

js/m3-components.js

Optional. Ripples, sliders, tab indicators, menus, snackbars, toolbars, app-bar collapse and more.

js/m3-pickers.js

Optional. Date, time and color pickers for fields marked data-m3-datepicker, data-m3-daterange, data-m3-timepicker or data-m3-colorpicker.

js/m3-springs.js

Optional. Real spring physics for switches and progress, so interrupted motion keeps its momentum.

js/m3-color.js

Theme building. HCT color science (a port of material-color-utilities).

js/m3-theme.js

Theme building. Turns a config into color schemes, a contrast report and theme CSS.

js/m3-customizer.js + css/m3-customizer.css

Theme building. The live customizer panel on this page.

assets/shapes/*.svg

Optional. Exact SVG outlines of the 35 Material shapes.

The material icon font (Material Symbols Rounded) is loaded from Google Fonts in the starter template. Any icon set works; the framework sizes .material-symbols-rounded and .material-symbols-outlined automatically.

Theming & customizer

A theme is a small config (seed color, scheme variant, contrast level, shape, type and motion) turned into CSS custom properties. Use the customizer on this page to build one, then export it.

Using the customizer

  1. Open it with the Customize button in the top bar. Every change applies live to this whole page.
  2. Pick a seed color, a scheme variant and a contrast level. Turn on Match exact brand color if the brand hex must appear as-is; it's used wherever it can still pass contrast.
  3. Adjust shape (corner scale and per-component corners), fonts and elevation.
  4. Export theme.css and include it after m3-bootstrap.css. The config is embedded in the file, so you can import it back later to keep editing.

Add the customizer to any page that loads the framework by including m3-color.js, m3-theme.js and m3-customizer.js (plus css/m3-customizer.css), then open it with:

Contrast guarantees

Each contrast level sets the minimum ratio for text and for UI parts. When a generated color misses the target against the surface it's drawn on, the generator shifts its tone until it passes, and the customizer's report lists any pair it had to adjust.

LevelTextUI (borders, icons, indicators)
Standard4.5:1 (WCAG AA)3:1
Medium7:1 (WCAG AAA)4.5:1
High11:17:1

Theme tokens

Everything the theme controls is a custom property, so you can read them in your own CSS:

Token familyExample
Color rolesvar(--md-sys-color-primary), var(--md-sys-color-on-surface-variant)
Type scalevar(--md-sys-typescale-title-medium-size), -weight, -line-height, -font
Shapevar(--md-sys-shape-corner-large)
Elevationvar(--md-sys-elevation-level2)
Motionvar(--md-motion-fast-spatial) (duration + spring easing, ready for transition)
State layersvar(--md-sys-state-hover-state-layer-opacity) (8%), -pressed- (10%)

Light & dark mode

The theme file contains both schemes. Bootstrap's data-bs-theme attribute picks one.

Set data-bs-theme="light" or "dark" on <html> (or any container, to theme just that part). With the customizer loaded, buttons carrying data-m3-mode switch modes and remember the choice:

To avoid a flash of the wrong mode on load, set the attribute before the page paints. This is the snippet this page uses in its <head>:

Bootstrap classes

Bootstrap's own classes keep working. Component modifiers are translated into M3; utility classes always win.

Component sizes

Bootstrap classBecomes
.btn-sm, .btn-group-smM3 extra-small button (32px)
.btnM3 small button (40px), the default
.btn-lg, .btn-group-lgM3 medium button (56px)
(no Bootstrap equivalent).btn-size-l (96px) and .btn-size-xl (136px)

Utilities

Bootstrap utilities are !important, so they override the framework every time. That's fine for layout, but on components it can switch off M3 behavior:

UtilityEffect on M3 components
m-*, w-*, d-*, flex-*, gap-*Safe. Nothing the framework animates is touched.
px-*Safe on buttons. Replaces the side padding (24px by default).
py-*Avoid on buttons: they grow past their M3 height. Use a size class.
rounded-*Mapped to M3 corners, but freezes the shape: no press morph, no toggle shape change.
shadow-sm / shadow / shadow-lgMapped to elevation levels 1 / 2 / 3, but block hover lift on elevated buttons and FABs.
bg-*, text-*M3 colors, but they override the hover and pressed tint. On buttons use the button variants instead.

Extra utilities

The framework adds M3-aware utilities alongside Bootstrap's:

ClassWhat it does
.bg-surface, .bg-surface-dim, .bg-surface-brightSurface background with its on-color text.
.bg-surface-container-lowest … -highestThe five surface containers, with on-surface text.
.bg-primary-container, .bg-secondary-container, .bg-tertiary-container, .bg-error-containerContainer colors with their on-container text.
.bg-surface-variant, .bg-inverse-surface, .bg-tertiaryOther roles, each with its paired text color.
.text-on-surface, .text-on-surface-variant, .text-outline, .text-tertiaryText color roles.
.elevation-0 … .elevation-5M3 elevation levels.
.rounded-m3-xs, -sm, -md, -lg, -xl, -fullM3 corner sizes.
.md-display-large … .md-label-smallType scale roles (see Typography).

Foundations

Color

M3 color roles, generated from your seed and contrast-checked. Each tile shows a role with the "on" color meant to sit on it. The hex values update as you change the theme.

Using color roles

Roles come in pairs: a background role and its on role for text and icons on top of it (primary / on-primary, surface-container / on-surface). Pair them as designed and contrast is guaranteed. Bootstrap's theme colors are mapped onto the roles, so .btn-primary, .text-bg-primary and .bg-primary all use your M3 primary.

Primary container
Secondary container
Tertiary container
Surface container high
Inverse surface

In your own CSS, use the custom properties directly:

Semantic colors

M3 has only an error role. The framework adds success, warning and info (each with container and on-colors) so Bootstrap's status classes keep working. By default they're harmonized, nudged slightly toward your seed hue, and they get the same contrast checks.

Typography

The M3 type scale: 15 roles, each also available in its Expressive emphasized weight. Fonts, scale and heading weight come from the theme.

Type classes

Apply a role with .md-<role>. Add .md-emphasized for the Expressive emphasized weight.

Headline small

Body large, for long-form reading.

Label large, emphasized

Bootstrap headings

Plain headings and Bootstrap's display classes are mapped onto the scale:

Element / classM3 role
h1, .h1display-small
h2headline-large
h3headline-medium
h4headline-small
h5title-large
h6title-medium
.display-2 / -3 / -4display-large / medium / small
.leadtitle-large in the regular weight
small, .smallbody-small

Shape

The M3 corner scale, scaled by the theme's corner setting. Components pick their corners from it (buttons full, cards medium, dialogs extra-large and so on).

Corner utilities

rounded-m3-xs
rounded-m3-md
rounded-m3-xl
rounded-m3-full

Bootstrap's .rounded, .rounded-1 … .rounded-pill map onto the same scale.

Shape library

The 35 M3 Expressive shapes, generated from Compose's MaterialShapes. Load css/m3-shapes.css and add .md-shape-<name> to clip any square element. The exact SVG outlines are in assets/shapes/.

Avatar in a 9-sided cookie Avatar in an arch

Shapes are drawn in a square. Give the element a 1:1 box (width and height, or aspect-ratio: 1) unless you want it stretched.

Names: circle square slanted arch fan arrow semi-circle oval pill triangle diamond clam-shell pentagon gem sunny very-sunny cookie-4 cookie-6 cookie-7 cookie-9 cookie-12 ghostish clover-4 clover-8 burst soft-burst boom soft-boom flower puffy puffy-diamond pixel-circle pixel-triangle bun heart.

Elevation

Six M3 elevation levels. Components use them automatically (elevated card 1, menu 2, FAB and snackbar 3); use .elevation-0 … .elevation-5 elsewhere.

elevation-1
elevation-3
elevation-5

Bootstrap's .shadow-sm, .shadow and .shadow-lg map to levels 1, 2 and 3. The theme's elevation style (M3, soft or none) changes every level at once.

Motion

M3 motion is defined as springs (damping and stiffness), not curves. The theme solves each spring and writes it as a CSS linear() easing, so overshoot and settle come from the physics and nothing needs JavaScript at runtime.

Playback speed (whole page)
Spring runtime

Spring tokens

Spatial springs move position, size and shape, and may overshoot. Effects springs drive color and opacity, and never overshoot. Click a card to play it; all plots share one time axis.

Using motion tokens

Each token is a ready-made transition value (duration and spring easing). Use spatial for anything that moves or changes size, effects for color and opacity:

TokenUse for
--md-motion-fast-spatialSmall, quick moves: switch handles, toggles, button shape morphs, menus opening.
--md-motion-default-spatialMost component motion: tab indicators, sheets, toolbars.
--md-motion-slow-spatialLarge, full-screen moves.
--md-motion-fast-effects / -default-effects / -slow-effectsColor, opacity and anything that mustn't overshoot.

Reduced motion and slow motion

Every duration is multiplied by --md-motion-scale. It's 0 when the visitor prefers reduced motion, so everything snaps. Set it higher to slow the page down while you study a transition (the playback speed buttons above do exactly that):

Components in motion

Stock markup, animated by the theme's springs.

Switch

The handle slides on fast-spatial and grows 16→24px (28px while pressed).

Shape morph on press

Hold a button down: its corners tighten, then spring back on release.

Progress values on a spring

Value changes ride default-spatial. Jump between values to see the spring.

Spring playground

Dial in any spring. The ball, switch and button in this panel use it in place of fast-spatial, so you can feel a value before it becomes a token, then copy the CSS.

Settles in
Overshoot
CSS

          

Icons

The framework is built around Material Symbols (Rounded or Outlined). Load the font, then use a span with the icon's name.

  • Inside buttons, chips, menus, nav items and fields, icons are sized and spaced for you; a button's first icon is treated as a leading icon.
  • Mark decorative icons aria-hidden="true". On icon-only buttons put the label on the button (aria-label).
  • Selected toggle icon buttons switch to the filled style automatically.

Actions

Buttons

Bootstrap .btnM3 buttons

Buttons let people take an action with one tap. Their emphasis tells people which action matters most.

Use it for

  • The most important action gets a filled button; the others step down to tonal, outlined or text
  • Short verb labels: "Save", "Send", "Add to cart"
  • One filled button per view where possible

Use something else

From Bootstrap: Bootstrap's variants keep working and map to M3 emphasis levels. Buttons become pill-shaped, sized from M3's five sizes, and tighten their corners when pressed.

Emphasis

Pick by how important the action is: filled for the main action, tonal for a secondary one, elevated when it has to stand off a busy background, outlined for medium emphasis, text for the least.

ClassM3 button
.btn-primaryFilled: primary / on-primary.
.btn-tonalFilled tonal: secondary-container.
.btn-elevatedElevated: surface-container-low, level 1 shadow, primary label.
.btn-outlined (also .btn-outline-primary, .btn-outline-secondary)Outlined: outline-variant border, on-surface-variant label.
.btn-text (also .btn-link)Text: primary label, no container.

Bootstrap variants

The other Bootstrap variants map to M3 roles. .btn-tertiary is added for the tertiary role. The outlined status variants keep their color, since M3 has no equivalent.

Sizes

Five Expressive sizes. Bootstrap's .btn-sm and .btn-lg map to extra-small and medium.

ClassHeightSide paddingIconLabel
.btn-size-xs / .btn-sm32px16px20pxlabel-large
default40px24px20pxlabel-large
.btn-size-m / .btn-lg56px24px24pxtitle-medium
.btn-size-l96px48px32pxheadline-small
.btn-size-xl136px64px40pxheadline-large

Square shape

Add .btn-square for the Expressive square button (12–28px corners depending on size).

Icons

Put a Material Symbols span first for a leading icon; the button tightens its padding on that side automatically. For a trailing icon, put it last and add .btn-icon-end.

Disabled

Use the disabled attribute (or .disabled with aria-disabled="true" on links). Disabled buttons use M3's on-surface 12% container and 38% content.

Text link

Press shape-morph and ripples

Pressed buttons tighten their corners and spring back on release (fast-spatial). With m3-components.js loaded they also get Compose's ripple. To opt an element out add data-m3-no-ripple; to give any element a ripple add data-m3-ripple.

Toggle buttons

Bootstrap .btn-checkM3 toggle buttons

Toggle buttons turn an option on or off, such as Bold or Favorite. The selected state changes color and shape.

Use it for

  • An option that applies right away, like Bold, Mute or Bookmark
  • Pair each with a .btn-check input so the state is a real checkbox or radio

Use something else

From Bootstrap: Bootstrap's .btn-check markup drives it. Selected buttons switch color and morph from round to square, as in M3 Expressive.

Use Bootstrap's toggle pattern: a hidden .btn-check input with a label.btn, or aria-pressed="true" / .active on a button you manage yourself.

BaseUnselectedSelected
.btn-primarysurface-container / on-surface-variantprimary / on-primary
.btn-tonalsecondary-containersecondary / on-secondary
.btn-elevatedsurface-container-low / primaryprimary / on-primary
.btn-outlinedtransparent / on-surface-variantinverse-surface

Icon buttons

M3 icon buttons

Icon buttons take a supplementary action with a single icon, in less space than a labeled button.

Use it for

  • Compact actions in toolbars, app bars, cards and list rows
  • Icons whose meaning is obvious; always add an aria-label

Use something else

  • An action that needs words to be clear: a button with a label
  • The screen's main action: a FAB

From Bootstrap: Bootstrap has no icon button. .btn-icon turns any .btn variant into one.

Sizes and widths

The five size classes apply. Each size also has narrow and wide forms: .btn-icon-narrow and .btn-icon-wide.

SizeIconNarrow / default / wide width
.btn-size-xs20px28 / 32 / 40px
default24px32 / 40 / 52px
.btn-size-m24px48 / 56 / 72px
.btn-size-l32px64 / 96 / 128px
.btn-size-xl40px104 / 136 / 184px

Toggle icon buttons

Add .btn-toggle. Selected icons switch to the filled style.

FABs

M3 FAB

The floating action button holds the most important action on a screen.

Use it for

  • One primary, constructive action per screen, such as Compose or Create
  • An extended FAB when a label makes the action clearer
  • A FAB menu for two to six related actions

Use something else

  • Destructive or minor actions: a button
  • Several equal actions: a toolbar

Sizes: .btn-fab-sm (40px), default (56px), .btn-fab-md (80px) and .btn-fab-lg (96px). Colors: default primary-container, .btn-fab-secondary, .btn-fab-tertiary, solid .btn-fab-primary and .btn-fab-surface.

Extended

Add .btn-fab-extended and a label. It works with every size and color. .btn-fab-collapsed shrinks it back to an icon (the label stays as the accessible name); press Collapse to see the width spring.

With m3-components.js, data-m3-fab-collapse collapses it while the page (or its scrolling container) scrolls down and expands it on the way back up. See it in the app bar demo.

Menu

A FAB that opens related actions. The FAB morphs into a close button and the items slide out one after another, their labels riding the growing edge. m3-components.js opens it from the toggle and closes it on Escape, an outside click or a choice.

Align the items to the FAB's position with .fab-menu-start or .fab-menu-center, or open it downward with .fab-menu-down for a FAB near the top:

ClassWhat it does
.btn-fab56px FAB, 16px corners, primary-container.
.btn-fab-sm / -md / -lg40px (12px corners) / 80px (20px, 28px icon) / 96px (28px, 32px icon).
.btn-fab-secondary / -tertiary / -primary / -surfaceSecondary or tertiary container; solid primary; surface-container-high with a primary icon.
.btn-fab-extendedAdds a label.
.btn-fab-collapsed / data-m3-fab-collapseCollapse an extended FAB to its icon / do it on scroll (script).
.fab-menu + .fab-menu-items + .fab-menu-toggleFAB menu; the script toggles .open.
.fab-menu-start / -center / -downItems left-aligned / centered / opening downward (not in Compose).

Button groups

M3 button groupsBootstrap .btn-group

Button groups gather related buttons. In M3 Expressive they react to each other: a pressed button grows and its neighbors make room.

Use it for

  • Related actions side by side: the standard group
  • Picking one or more options from a set: the connected group, which replaces M3's segmented buttons

Use something else

  • Many actions on a page: a toolbar
  • Switching between views of content: tabs

From Bootstrap: A plain .btn-group of .btn-check toggles renders as an outlined segmented button.

Standard group

.btn-group-standard spaces buttons 12px apart. A pressed button grows about 15% and its neighbors give way, on the fast spring.

Connected group

.btn-group-connected replaces M3's segmented buttons: 2px gaps, round outer corners, inner corners that tighten when pressed and go fully round when selected. Usually holds .btn-toggle buttons.

Add .w-100 to stretch the buttons evenly across the row.

Segmented buttons (Bootstrap button group)

A Bootstrap .btn-group of .btn-check toggles renders as an outlined segmented button, with a check icon on the selected segment.

Split button

M3 split button

A split button pairs a main action with a menu of related ones.

Use it for

  • A clear default with occasional alternatives, like Save and Save as

Use something else

From Bootstrap: Like Bootstrap's split dropdown, with M3 shapes: the inner corners round on hover and press, and the chevron flips while the menu is open.

It uses Bootstrap's dropdown plugin, so the menu needs Bootstrap's JS bundle. Any button variant and size works on both halves (keep them the same).

Communication

Snackbars

M3 snackbarNeeds m3-components.js

Snackbars give a brief update about something the app did or will do, at the bottom of the window. People can keep working without touching them.

Use it for

  • Low-priority confirmations: "Message archived", "Photo saved"
  • At most one action, such as Undo or Retry
  • Short text, one line where possible; no icons or links

Use something else

  • Something that needs a decision: a dialog
  • A status that should stay put: an alert
  • Several ongoing uploads or downloads: the transfer panel

From Bootstrap: Bootstrap toasts are placed and stacked by hand. Snackbars queue one at a time from a single call, pause while hovered or focused, and report whether the action was used. (Bootstrap's .toast also picks up the snackbar look.)

On the web, a snackbar that disappears on its own can be missed by people with low vision or who need more time. M3 asks for one of two fixes: also show the message near what triggered it (the Save button turns to "Saved"), or give the snackbar an action so it stays until used.

Each one scales in from 80% and fades in; the next waits until the current one is dismissed (the queue button shows this). On screens 600px and wider they sit at the bottom-left corner (bottom-right in right-to-left languages), sized to their message, as in Google's web apps; on phones they're centered. Short snackbars leave after 4 seconds; ones with an action stay until you act or close them, as in Compose. Hovering or focusing a snackbar pauses its timer.

From JavaScript

Attribute (on any clickable element)Option
data-m3-snackbar="…"The message; clicking shows the snackbar.
data-m3-snackbar-action="…"Action button label.
data-m3-snackbar-durationshort, long, indefinite or milliseconds.
data-m3-snackbar-dismissibleAdd a close button.
data-m3-snackbar-stackedAction on its own line.
data-m3-snackbar-align="center"Keep it centered on wide screens too.

Tooltips

Bootstrap .tooltip / .popoverM3 tooltips

Tooltips show a brief label or explanation when people hover, focus or long-press an element.

Use it for

  • Plain tooltips to name an icon-only button
  • Rich tooltips for a short explanation, with an optional action

Use something else

  • Information people need to get their task done: show it on the page
  • News about something that just happened: a snackbar

From Bootstrap: Tooltips become M3 plain tooltips and popovers become rich tooltips. No caret by default, and rich tooltips close on an outside click.

Placement

Use Bootstrap's data-bs-placement. Like Compose, tips have no caret, sit 4px from their anchor and grow out from the side that faces it.

Optional caret

M3 leaves the caret off by default (Compose's caretShape is null). To point at the anchor, add data-bs-custom-class="tooltip-caret". It works on tooltips and popovers, and the caret is 16 × 8, Compose's TooltipDefaults.caretSize.

Rich tooltip with an action

Put an action in .popover-actions with data-bs-html="true". Bootstrap's sanitizer keeps links, so use an <a class="btn btn-text">.

Dismissal and motion

With m3-components.js loaded, a rich tooltip opened by click stays open until you click outside it or press Escape, as Compose's persistent tooltips do. Clicks inside it, on its action for example, keep it open. Tips scale from 0.8 to 1 on the fast-spatial spring while fading on fast-effects, and sit 4px from their anchor. The script sets this up through bootstrap.Tooltip.Default, so load it before you create your tooltips.

As in Bootstrap, tooltips and popovers are opt-in; initialize them in your script:

PartM3 token
.tooltipinverse-surface container, inverse-on-surface body-small text, extra-small corners, 200px max width.
.popoversurface-container, medium corners, level-2 shadow (caret included), 320px max width.
.popover-headerSubhead: title-small, on-surface-variant.
.popover-bodySupporting text: body-medium, on-surface-variant.
.popover-actionsAction row; put .btn-text buttons in it (primary label).
.tooltip-caretVia data-bs-custom-class: shows the optional 16 × 8 caret.

Badges

M3 badgesBootstrap .badge

Badges show counts or status on icons and navigation items.

Use it for

  • Unread counts on navigation items and icon buttons
  • A small dot for something new

Use something else

  • A label inside text, like "New": a word badge, below
  • A message about what happened: a snackbar

From Bootstrap: .badge stays as a word badge; .badge-count and .badge-dot are M3's.

On icons

M3 badges always sit on something. Wrap the icon and its badge in .badge-anchor: a count badge (.badge.badge-count, 16px, label-small) or a 6px .badge-dot. They're placed as Compose's BadgedBox places them and use the error color by default. Nav bar and rail items do this on their own.

The badge is visual only, so put the count in the button's aria-label ("Mail, 3 unread"), as above.

Word badges

Bootstrap's .badge is a label for text: label-medium in a 20px pill, colored with .text-bg-*. M3 has no word badge; these are kept from Bootstrap for inline use.

Primary Secondary Success Warning Danger

Release notes New

ClassWhat it does
.badge-anchorWraps an icon and places its badge on the top end corner (Compose BadgedBox).
.badge.badge-countM3 large badge: 16px, label-small, error color.
.badge-dotM3 small badge: a 6px error dot.
.badgeBootstrap word badge: label-medium in a 20px pill; color it with .text-bg-*.

Progress indicators

Bootstrap .progressM3 progress indicators

Progress indicators show that a process is running and, when known, how far along it is.

Use it for

  • Determinate when you know the percentage; indeterminate when you don't
  • Linear for pages and sections; circular inline or in small spaces

Use something else

From Bootstrap: .progress becomes M3's flat linear indicator, with a gap and a stop dot. The wavy and circular indicators are new.

Flat linear

A 4px gap separates the indicator from the track, which ends in a stop dot.

Wavy

Set the value with --md-progress-value (a percentage) and keep aria-valuenow in step. The wave flattens near 0% and 100%, as in Compose.

Indeterminate

ClassWhat it does
.progress-wavyWavy linear indicator.
.progress-circular (+ -sm, -lg)Wavy circular indicator: 32 / 48 / 72px.
.progress-indeterminateLooping version of either.

Value changes animate on a spring (see Motion). With m3-components.js the wave is drawn as SVG each frame, giving round caps and smooth amplitude changes.

Loading indicator

M3 loading indicator

The loading indicator shows that something is loading, for short waits.

Use it for

  • Waits from about 200ms to a few seconds, such as refreshing a feed
  • In place of a circular indeterminate progress indicator

Use something else

From Bootstrap: It replaces Bootstrap's spinners, which still work (see the end of this section).

Determinate

Morphs from a circle toward a soft burst, turning half a revolution, as --md-loading-progress goes from 0 to 1. It only moves when the progress changes: set the property (and aria-valuenow) from your loading code. With m3-components.js each change glides on Compose's progress spring, so uneven updates still morph smoothly; without it the shape steps. Drag the slider or simulate a load:

40%

Bootstrap spinners

Bootstrap's .spinner-border and .spinner-grow still work and take the primary color. They're plain Bootstrap, not M3; prefer the loading indicator above for short waits and a circular progress indicator when you know how far along a task is.

Loading…
Loading…
Loading…
Loading…
ClassWhat it does
.loading-indicator48px indeterminate indicator.
.loading-indicator-containedOn a primary-container circle.
.loading-indicator-sm / -lg32px / 72px.
.loading-indicator-determinateFollows --md-loading-progress (0–1).

Color it with --md-li-color (and --md-li-container for the contained one). It stops when the visitor prefers reduced motion.

Alerts

Bootstrap .alertNo M3 equivalent

Alerts show a message in the page that stays until it's dismissed or resolved.

Use it for

  • A status that should stay visible, like a failed sync or an expiring trial

Use something else

  • A brief confirmation: a snackbar
  • Something that needs a decision: a dialog

From Bootstrap: Same markup on M3 container colors. M3's closest equivalents are banners and cards.

Primary alert

On-primary-container text on primary-container.
Success: harmonized toward the seed.
Warning: still contrast-checked.
Info alert with a link.
Danger, mapped to M3 error.

Transfer panel

Addition, not in M3Needs m3-components.js

A Drive-style panel that tracks several uploads or downloads at once, in the corner of the window.

Use it for

  • Several longer transfers that people may want to watch or cancel

Use something else

From Bootstrap: Built on a Bootstrap .toast, which M3 doesn't stack, so this is our own addition.

It's not an M3 component. Unlike a snackbar it stays up and holds several rows, so it follows the theme like a dialog or menu: surface-container-high, level-3 shadow and large corners, as Google Drive's upload panel does. The header counts what's in flight ("Uploading 3 items", "2 items complete · 1 failed") and has collapse and close buttons. Drive it from your upload code:

Containment

Cards

Bootstrap .cardM3 cards

Cards hold content and actions about a single subject.

Use it for

  • Grouping related information and actions
  • Elevated, filled or outlined, depending on how much a card should stand out

Use something else

  • A list of similar items: a list
  • Tabular data: a table

From Bootstrap: .card is the outlined card; .card-elevated and .card-filled add the other two.

Elevated

surface-container-low

Level 1 shadow, rising to 2 when it's interactive.

Action

Filled

surface-container-highest

For grouping without lifting content off the page.

Header

Outlined

Bootstrap's default .card

An outline-variant border on the surface color.

Clickable cards (an a.card, or a .card with .card-action) lift on hover as in Compose.

Imagery

In M3, images live inside shaped containers, and any text on them sits on a scrim so it stays readable.

M3 has no imagery component. Its guidance is spread across cards, carousels, color and accessibility, and these classes gather it: .media crops an image into its container, .media-text puts protected text on it, .media-hero and .media-parallax make header images, and data-m3-image-color takes colors from the image itself.

Images in cards

The card's shape clips the image: no border, radius or shadow on the image itself. Wrap it in .media with one of Bootstrap's .ratio classes for the crop, and move the focal point with object-position when the subject isn't centered. An image can top a card, or fill it with the text on the image.

A rowing boat on a turquoise mountain lake

Image on top

A 16:9 crop at the top of an outlined card. The text stays on the card's surface.

A green valley under low cloud at sunrise

Focal point

object-position: 50% 70% keeps the valley in frame instead of the sky.

Image fills the card

Text on the image gets a scrim.

Text on images

Never put text straight on a photo: what's readable on one image disappears on the next. .media-text lays a scrim behind the text. It's black at 60% under the text itself, which keeps white text above 4.5:1 even on a pure white image, then fades out over 96px on an eased curve, so there's no hard edge. That follows Material's text-protection guidance: a long gradient with most of its change near the dark end. .media-text-top puts the text at the top, .media-text-fill dims the whole image for centered text, and .media-text-tinted uses a dark shade of the image's own color instead of black (see color from images).

A pale sandy beach at sunset

Bottom

On the brightest part of the photo

A performer with a raised hand in white stage smoke

Top

Over white smoke

Fill

For centered text

Header images and parallax

.media-hero is a tall header image (240–440px, following the window height) with Expressive extra-large corners; add .rounded-0 for full bleed. Add .media-parallax and the image drifts more slowly than the page as it scrolls by.

Does M3 still use parallax? Yes, but in a narrower way than the old full-page background effect. In M3's carousel, each item is a window onto its image; as an item shrinks toward the edge, its image moves the other way, so it looks pressed against the edge rather than cut off. Android's collapsing image headers do the same. A page background fixed behind everything isn't an M3 pattern. .media-parallax is the header version: CSS scroll-driven animation, no JavaScript. Browsers without it, and people who ask for reduced motion, get a still image.

Snow-capped peaks reflected in a still blue lake at dusk

Destinations

The Canadian Rockies

Ten lakes, three passes, one very long drive.

Color from images

M3 calls this content-based color: colors drawn from the content on screen rather than the app theme. Spotify's album pages are the familiar case, with the cover's color washing down into the page. Add data-m3-image-color to a container and the script picks the image's most telling vivid color, using the idea behind Material's color scoring: how much of the image is near that hue, and how colorful it is. It sets --md-image-color, and the color fades in when the image loads. Then:

  • .image-gradient: the color at 35% over the surface, fading down into the plain surface (the album-page look).
  • .image-tint: a quiet 16% tint of surface-container, for cards.
  • .media-text-tinted: the scrim in a dark shade of the color.

The text stays in the theme's own on-surface color. Mixing at most 35% of any color into the surface keeps that text above 4.5:1, light or dark. The image must be on your own site or sent with CORS (crossorigin="anonymous"); otherwise the browser won't let the script read it, and the colors stay neutral. You can also set --md-image-color yourself, from the server, and skip the script.

Album cover: orange, pink and blue liquid bubbles

Album

Surface Tension

The Material Ensemble · 2026 · 9 songs, 38 min

Meniscus
The Material Ensemble
Capillary Action
The Material Ensemble
Bubble Wrap
The Material Ensemble

Each card below takes its own tint from its cover:

Festival Mix

Big rooms, bigger drops

Late Night

Low light, deep cuts

Golden Hour

Warm and loud

Riff Raff

Guitars, mostly

Carousels

M3's carousel is the main place it moves images around: items in a row that shrink as they reach the edges, with the parallax described above. It comes in multi-browse, uncontained, hero and full-screen layouts. M3 warns against shrinking items so far that you can't tell what's in the picture. The carousel just above has the multi-browse, uncontained and hero layouts.

Guidelines

Do

  • Give meaningful images real alt text; use alt="" when the image is decoration
  • Set width and height (or a .ratio) and loading="lazy", so the page doesn't jump as images arrive
  • Keep the subject in frame on every screen size with object-position

Don't

  • Put text on an image without a scrim, or rely on the photo being dark enough
  • Add borders, shadows or separate corner rounding to images inside cards
  • Use a fixed full-page background image as a parallax backdrop
Attribute or classWhat it does
.media + .media-imgAn image cropped to fill its container (object-fit: cover). Pair with .ratio-* or a height.
.media-textText on the image with a scrim: 60% black under the text, then an eased 96px fade.
.media-text-top / -fill / -tintedText at the top / the whole image dimmed / the scrim in the image's own dark color.
.media-heroA 240–440px header image with extra-large corners.
.media-parallaxThe image drifts more slowly than the page (CSS scroll-driven; still if unsupported or reduced motion).
data-m3-image-colorSets --md-image-color from an image inside (or the one the value selects). Needs m3-components.js.
.image-gradient / .image-tintThe image color washed into the surface / a light tint of surface-container. Text keeps 4.5:1.
.card-img-overlayBootstrap's overlay, bottom-aligned with a scrim.

Media viewer

No M3 equivalentNeeds m3-components.js

The media viewer shows images, video and other content full screen, one at a time, with the rest of the gallery a swipe away.

Use it for

  • Photo galleries, product photos, before and afters
  • Videos, YouTube or Vimeo, and documents opened from a page without leaving it

Use something else

  • Browsing a few images in place: a carousel
  • A form or a decision: a dialog

From Bootstrap: Bootstrap has no lightbox, and M3 has no media viewer component. This one works like Fancybox (data attributes on the links you already have, grouped into galleries) but is built from M3 parts: a full-screen black stage, icon buttons, M3's container transform to open and close, shared axis between items and a carousel of the gallery. No other library is needed.

Gallery

Put data-m3-viewer on a link to the full-size image, around its thumbnail. Links with the same value make one gallery, in page order. A click opens the picture straight out of its thumbnail (the rounded crop grows into the whole image) with the rest of the gallery in a strip below it; closing shrinks it back into the thumbnail it's showing. data-caption goes under the picture (or use a <figure> and its <figcaption>). Double-click, double-tap, + or the zoom button zooms to the image's own size; drag to look around.

A rowing boat on a turquoise mountain lake Snowy peaks reflected in a blue lake at dusk A green valley under low cloud A sandy beach at sunset Sunlight through a pine forest A road winding through hills toward mountains

Slideshow and links

The round play button in the top bar runs the gallery as a slideshow: a thin bar over the strip fills over each item's time, then the next comes in, looping at the end. A video plays to its end first; YouTube, Vimeo and pages wait for you to move on. It holds while you zoom or drag, and the button pauses it (WCAG asks for a way to stop anything that moves on its own). data-slideshow="4000" sets the time per item (5 seconds otherwise) and data-autoplay starts it on opening; put them on any item of the gallery, as on the first one above.

While a named gallery is open, the address shows which item (#places-3): copy it to share that picture, and the page opens the gallery there. Back closes it. data-hash="false" leaves the address alone. Try #places-4.

Video, embeds and HTML

The viewer works out what each link is from its address: a video file (.mp4, .webm), a YouTube or Vimeo page (played with YouTube's no-cookie player), a .pdf, or #id for something on the page, shown on an M3 surface card; anything else is an image. data-type says so outright (image, video, youtube, vimeo, iframe, pdf, html), and data-width / data-height size a video or frame (16:9 otherwise). Videos start playing and stop when you move on.

From code

M3Components.viewer(items, index) opens it with any list; each item takes the same things as the attributes. It takes { thumbs, slideshow, autoplay, hash } as a third argument and returns an object with el, go(i), next(), prev(), play(), pause() and close(), and the viewer sends m3:viewer-open, m3:viewer-change and m3:viewer-close on the document (detail: index, item, items).

Keyboard: ← → move, Home / End go to the first or last, + / - zoom, Esc closes; Tab reaches the buttons and the strip, whose items take the arrow keys and Enter. It's a native <dialog>, so focus stays inside, the page behind is inert, and focus goes back to the link afterwards. A link with a modifier key held (Ctrl, Cmd, Shift) opens normally. With reduced motion nothing moves.

AttributeWhat it does
data-m3-viewerOn a link or button: opens the viewer. The same value makes a gallery; empty shows it alone. (Anything else needs tabindex="0"; it gets Enter and Space.)
href / data-srcWhat's shown (the full-size image, the video, the page, #id).
data-typeimage, video, youtube, vimeo, iframe, pdf or html; read from the address otherwise.
data-captionText under the item (or the figure's <figcaption>).
data-titleText in the top bar, next to the count.
data-width / data-heightA video's or frame's size (its shape is what counts; 16:9 otherwise).
data-thumbThe picture for it in the strip (the <img> inside otherwise).
data-downloadAdds a download button; the value names the file.
data-thumbs="false"No strip.
data-slideshow / data-autoplayThe slideshow's time per item in ms (5000 otherwise) / start it on opening. On any item of the gallery.
data-hash="false"Don't show #name-3 in the address (and don't open from it).

Dialogs

Bootstrap .modalM3 dialog

Dialogs ask for a decision or show critical information, blocking the page until the person responds. They are interruptive on purpose, so use them sparingly.

Use it for

  • Confirming a high-risk action, like discarding work or deleting
  • A single, short task that needs an answer before going on
  • A clear question for a headline, never "Are you sure?"; put the confirming action last

Use something else

  • Low or medium priority news: a snackbar
  • Choosing from a few options without interrupting: a menu
  • A message that should stay on the page: an alert

From Bootstrap: Same .modal markup and JavaScript. Dialogs are always centered and fit their content between 280 and 560px instead of a fixed 500px; the headline, text and actions follow M3; icon, scrolling and full-screen variants are added.

Basic

With an icon

Put a .modal-icon before the title. The icon (secondary, 24px) and the headline center above the text.

Scrolling content

Add Bootstrap's .modal-dialog-scrollable. The headline and actions stay put, and dividers mark where the content scrolls under them.

Form

A dialog holding fields should use its full 560px. Add .modal-dialog-wide (Bootstrap's .modal-sm, -lg and -xl also set a fixed width). Fields inside take the dialog's container color.

Full-screen

M3's full-screen dialog is Bootstrap's .modal-fullscreen: surface color, no corners, and a top bar with a close icon button, a title-large headline and a text-button action. Any .modal-header that starts with an icon button gets the bar layout. M3 recommends full-screen dialogs only on compact screens, so .modal-fullscreen-sm-down is usually the right choice. The example uses .modal-fullscreen so you can see it here.

Requires an answer

With Bootstrap's data-bs-backdrop="static" and data-bs-keyboard="false", clicking the scrim or pressing Escape doesn't close the dialog, so the person has to pick an action.

ClassWhat it does
.modal / .modal-dialog / .modal-contentBasic dialog: centered, fits its content between 280 and 560px, surface-container-high, 28px corners.
.modal-titleHeadline: headline-small, on-surface.
.modal-bodySupporting text: body-medium, on-surface-variant.
.modal-footerActions, right-aligned, 8px apart; use .btn-text.
.modal-iconHero icon above a centered headline.
.modal-dialog-scrollableBody scrolls between dividers.
.modal-dialog-wideTakes the full 560px (forms).
.modal-fullscreen / -sm-down …Full-screen dialog: surface, no corners, top bar header.

Sheets

Bootstrap .offcanvasM3 bottom & side sheets

Sheets show supporting content anchored to an edge of the window: the bottom on phones, the side on larger screens.

Use it for

  • Secondary content or actions on a phone: a bottom sheet
  • Filters or details beside the main content on larger screens: a side sheet

Use something else

From Bootstrap: .offcanvas-bottom, -end and -start become M3 bottom and side sheets, sliding on a spring.

Bottom sheet

Bootstrap's .offcanvas-bottom becomes the M3 bottom sheet: a drag handle, 28px top corners, up to 640px wide, sliding on a spring. Its height follows its content.

Side sheet

Secondary content anchored to the side of the window. A modal side sheet is Bootstrap's .offcanvas-end (or .offcanvas-start), sliding over the page with a scrim. A standard side sheet sits in the layout beside the content.

Modal

Up to 400px wide on surface-container-low, with large corners on the inner edge and a level-1 shadow. The header holds an optional back button, the title-large headline and a close button. The optional .offcanvas-footer keeps its actions at the bottom; add .border-top for the divider.

Filters

Narrow the results by type and date.

Detached

Add .offcanvas-detached to float the sheet 16px from the window edges, with all four corners rounded.

Filters

Narrow the results by type and date.

Standard

A standard side sheet is part of the layout, not an overlay: .side-sheet on surface, flat, with an optional .border-start divider. Put it in a flex row next to your content. To open and close it, wrap it in Bootstrap's .collapse.collapse-horizontal. The sheet keeps its full width and slides out past the window edge as the content takes its space, so nothing inside it reflows. Its width eases without overshoot, so the content beside it never wobbles. For a sheet on the start side, add .side-sheet-start and use .border-end.

Page content. It takes whatever width the sheet leaves.

ClassWhat it does
.offcanvas-end / .offcanvas-startModal side sheet: surface-container-low, level 1, large inner corners, max 400px.
.offcanvas-detachedFloats the modal sheet 16px from the edges, all corners rounded.
.offcanvas-header72px header: optional leading .btn-icon (back), .offcanvas-title (title-large, on-surface-variant), .btn-close.
.offcanvas-bodyContent, 24px side padding, on-surface-variant.
.offcanvas-footer72px action row at the bottom, left-aligned; add .border-top for the divider.
.side-sheetStandard side sheet: in-page, surface, flat. Add .border-start / .border-end for the divider.
.side-sheet-startA standard sheet on the start side, so it slides out toward that edge.

Lists

Bootstrap .list-groupM3 lists

Lists are continuous, vertical groups of text or images that are easy to scan.

Use it for

  • Items of the same kind, in one, two or three lines

Use something else

  • Mixed content with actions: cards
  • Comparing values across columns: a table

From Bootstrap: .list-group with M3 item heights (56, 72 or 88px) and state layers. The segmented list is new.

Segmented list

Add .list-group-segmented: items 2px apart whose corners round off on hover and when pressed or selected.

ClassWhat it does
.list-group-item-two-line / -three-line72px / 88px items.
.list-group-segmentedExpressive segmented list. Item color via --md-list-item-bg.

Accordion

Bootstrap .accordionNo M3 equivalent

Accordions stack collapsible sections, so long content can be scanned by heading.

Use it for

  • FAQs and long pages split into sections people open one at a time

Use something else

  • Switching between views of equal weight: tabs
  • A few short sections: just show them

From Bootstrap: Same markup. M3 colors and shape; the panel opens on a no-bounce spring, so nothing below it moves past its final position, and the chevron turns on the bouncy one.

Colors come from HCT tonal palettes, where tone is L*. Every pairing the framework draws is measured, and any foreground below target is moved in tone until it passes.

No. Plain CSS custom properties and vanilla JS. The customizer writes a theme.css you drop in after the framework.

Tables

Bootstrap .tableNo M3 equivalent

Tables show data in rows and columns, so values are easy to compare.

Use it for

  • Comparing several values across many items
  • Sorting, selecting and paging through records

Use something else

  • Single-column items: a list
  • Mixed content with images and actions: cards

From Bootstrap: Every Bootstrap table class works. Headers use M3 title-small, rows sit on the surface, and hover and selection use M3 state layers.

Basic

ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240

Striped and hoverable

Add .table-striped for alternating rows and .table-hover for an M3 hover state layer.

ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240

Bordered, borderless and small

ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240
ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240
ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240

Row colors

Bootstrap's contextual classes use the M3 container colors, so text keeps its contrast: .table-primary, -secondary, -success, -warning, -danger, -info, -light and -dark. They work on a whole table, a row or a cell.

ClientPlanStatusMRR
Northwind StudioStudioActive$240
Acme Co.StarterTrial$0
GlobexAgencyActive$480
InitechStudioPast due$240

Data table

A fuller pattern in a card: a toolbar with a title and actions, selectable rows, a sortable column and a footer with rows per page and paging. Selected rows use .table-active (secondary-container). For sorting, put a .table-sort button in the header cell and set aria-sort on the th. The selecting and sorting in this demo come from the page's demo script; wire them to your own data.

Team

Email
Ava MartinezDesignava@example.comOct 2, 202612
Ben OkaforEngineeringben@example.comSep 18, 202631
Chloe NguyenMarketingchloe@example.comOct 9, 20267
Diego AlvarezEngineeringdiego@example.comAug 27, 202624
Emma SchultzSupportemma@example.comOct 14, 20263

Wide tables

Wrap a table in .table-responsive and it scrolls sideways on narrow screens instead of widening the page. Add a <caption> to describe it (.caption-top puts it above).

Monthly usage by site
SiteJanFebMarAprMayJunJulAugTotal
zephyrworks.com1,2041,3221,4101,3881,5021,6111,5901,70211,729
4beam.com4123984554705024885305613,816
vicariouspr.com2202412362592702662813022,075
ClassWhat it does
.tableTitle-small headers (on-surface-variant), 12px × 16px cells, outline-variant dividers.
.table-striped / .table-hoverAlternating rows / hover state layer.
.table-bordered / .table-borderless / .table-smAll borders / none / compact 6px × 12px cells.
.table-primary … .table-darkRow, cell or table colors from the M3 container roles.
.table-activeSelected row: secondary-container.
.table-sort + th[aria-sort]Sortable header button; the arrow shows and turns with aria-sort.
.table-toolbar / .table-toolbar-title / .table-footerToolbar above and footer below a table in a card. .is-selecting tints the toolbar while rows are selected.

Navigation

App bars

M3 top app barsJS for scrolled color and collapse

Top app bars show a page's title and its main actions at the top of the screen.

Use it for

  • Small bars for most pages; medium or large flexible bars for a prominent title that collapses on scroll
  • Two or three actions; put the rest in a menu

Use something else

  • Many actions: a toolbar
  • A marketing site header: Bootstrap's .navbar

From Bootstrap: Bootstrap's .navbar is a site header. App bars are app-style page headers with M3 sizes and scroll behavior.

Small

Small app bar

Medium and large flexible

The title moves into .app-bar-headline below the row. .app-bar-scrolled switches the container to surface-container (the "content is scrolled under me" color).

Medium flexible

With subtitle, scrolled color

Large flexible

Display-small title

Class / attributeWhat it does
.app-barSmall bar: 64px row, title-large title.
.app-bar-medium112px (136 with subtitle), headline-medium title.
.app-bar-large120px (152 with subtitle), display-small title.
.app-bar-row / .app-bar-actions / .app-bar-headlineTop row, trailing actions, title block.
.app-bar-title / .app-bar-subtitleTitle and subtitle.
.app-bar-scrolledScrolled container color.
data-m3-app-barScript adds .app-bar-scrolled once the page scrolls.
data-m3-app-bar="collapse"Script collapses a medium or large bar as you scroll (below).

Collapse on scroll

With data-m3-app-bar="collapse" on a medium or large bar, the headline slides up under the top row as you scroll and the small title fades in; stop halfway and it snaps open or shut. Add a small .app-bar-title to the row for the collapsed state. It works in the page or in any scrolling container. Scroll inside the frame:

Inbox

18 folders

    The FAB in the frame uses data-m3-fab-collapse (see Extended FAB).

    Toolbars

    M3 toolbarJS for expanding and hide on scroll

    Toolbars hold actions for the current page. A docked toolbar spans the window for actions that stay the same across pages; a floating toolbar hovers over the content for actions about what's on screen.

    Use it for

    • Actions about the current page or selection
    • Standard colors to keep attention on the content; .toolbar-vibrant to emphasize the controls or signal a mode, such as editing
    • More actions than fit: open the rest from a menu

    Use something else

    • Moving between top-level pages: a navigation bar. Never show it together with a docked toolbar
    • One main action for the screen: a FAB
    • A title with a few actions at the top: an app bar

    From Bootstrap: Bootstrap's .btn-toolbar only spaces button groups. These are M3 surfaces with their own color, shape and motion.

    Docked

    .toolbar-docked is flat and full width: 64px on surface-container with square corners, pinned to the bottom of the screen or a scrolling area. Pin it with Bootstrap's .fixed-bottom for the page, or .sticky-bottom inside a scroller as here. As in Compose, items spread edge to edge inside 16px end padding, never closer than 4px. Add .toolbar-docked-fixed to center them with 32px between instead.

    Add data-m3-toolbar="exit-always" to have it slide away as the content scrolls down and come back as it scrolls up. This is Compose's exitAlwaysScrollBehavior: the bar follows the scroll 1:1, and if you stop halfway it snaps shown or hidden on fast-spatial. Tabbing into it always brings it back. Scroll inside the frame:

      Add .toolbar-vibrant for the primary-container version:

      Screen content

      Floating

      Expanding

      A floating toolbar that springs out of a FAB, or opens out both sides of a middle toggle. The icons slide out on the moving edge, start bunched up and spread to their spacing. The script opens them and keeps the hidden buttons out of the Tab order until then.

      Beside a FAB

      Press the FAB. The toolbar opens to the left (right to left). The FAB's icon turns into a close icon; give it both icons as shown. Hidden toolbar buttons can't be reached by Tab until it opens.

      Left to right and vertical

      .toolbar-fab-start
      .toolbar-fab-vertical

      Both sides of a toggle

      .toolbar-expandable keeps a middle toggle visible and opens a group on each side, so the toolbar grows out from its center.

      ClassWhat it does
      .toolbar-dockedFull-width 64px bar on surface-container; items spread edge to edge.
      .toolbar-docked-fixedCenters the items with 32px between them.
      data-m3-toolbar="exit-always"On .toolbar-docked: hides while scrolling down, returns while scrolling up (needs m3-components.js).
      .toolbar-floating64px pill, 8px padding, level-3 shadow.
      .toolbar-vibrantPrimary-container colors.
      .toolbar-verticalA floating toolbar stacked vertically.
      .toolbar-fabWrapper for a .toolbar-floating plus its FAB toggle. Opens right to left.
      .toolbar-fab-startFAB on the left; opens left to right.
      .toolbar-fab-verticalFAB at the bottom; a vertical toolbar opens upward.
      .toolbar-fab-toggleThe FAB that toggles it (needs aria-controls).
      .toolbar-expandableOn a .toolbar-floating: two .toolbar-groups open around a middle toggle.
      .toolbar-expand-toggleThe middle toggle button.
      .toolbar-toggle-icon / -closeThe toggle's two icons; they turn and cross-fade.
      .openThe open state (set by the script).

      Tabs

      Bootstrap .nav-tabsM3 tabs

      Tabs organize related content into views at the same level, so people can switch without leaving the page.

      Use it for

      • Primary tabs for the main views of a page; secondary tabs for views within one of them
      • Short labels, and no more tabs than fit comfortably

      Use something else

      From Bootstrap: .nav-tabs becomes M3 primary tabs. With m3-components.js one indicator slides between tabs on a spring.

      Primary

      Secondary

      Add .nav-tabs-secondary: a 2px indicator spanning the whole tab, for tabs within a section.

      The indicator follows the .active link. Bootstrap's tab plugin (data-bs-toggle="tab") moves it for you; the demo tabs here move it on click.

      Pills

      Bootstrap's .nav-pills render as M3-colored pills (secondary-container when active).

      Pagination

      Bootstrap .paginationNo M3 equivalent

      Pagination splits a long set of results into pages.

      Use it for

      • Search results and tables where people jump to a specific page

      Use something else

      • Feeds people read top to bottom: load more as they scroll
      • A handful of items: show them all

      From Bootstrap: Same markup, as M3 buttons: 40px round targets, a hover state layer and the current page on secondary-container.

      Many pages

      Skip a range with an ellipsis item: .page-item-ellipsis keeps it from looking clickable.

      Sizes and alignment

      .pagination-sm (32px) and .pagination-lg (48px); align with Bootstrap's flex utilities such as .justify-content-center.

      ClassWhat it does
      .pagination / .page-item / .page-link40px round page buttons, 4px apart.
      .page-item.activeCurrent page: secondary-container.
      .page-item.disabledDisabled content color.
      .page-item-ellipsisA non-interactive … gap.
      .pagination-sm / .pagination-lg32px / 48px buttons.

      Selection

      Checkboxes & radios

      Bootstrap .form-checkM3 checkbox & radio

      Checkboxes let people pick any number of options; radio buttons pick exactly one.

      Use it for

      • Checkboxes for several selections, or to agree to something
      • Radio buttons for one choice from a short, visible list

      Use something else

      • A single setting that takes effect immediately: a switch
      • A long list of options: a select field

      From Bootstrap: Same .form-check markup. M3 sizes, a checkmark that draws in, a springing radio dot and state layers on hover, focus and press.

      The indeterminate state is set from JavaScript (input.indeterminate = true), as on any checkbox. The demo uses data-indeterminate to do that for you; it's not a framework attribute.

      Switches

      Bootstrap .form-switchM3 switch

      Switches turn a single setting on or off, and the change takes effect immediately.

      Use it for

      • Settings that apply at once, like Wi-Fi or dark mode
      • A label that says what the switch turns on

      Use something else

      From Bootstrap: Same .form-switch markup, as the M3 52×32 switch: the handle slides on a spring, grows when on and while pressed, and can carry icons.

      With icons

      Add .form-switch-icons to the wrapper: the handle carries a check when on and a close icon when off.

      With m3-springs.js loaded, call M3Springs.refresh(input) after changing .checked from code.

      Sliders

      Bootstrap .form-rangeM3 slider

      Sliders let people pick a value, or a range, along a scale.

      Use it for

      • Settings where the exact number matters less than the feel, like volume or brightness
      • Ticks for a few discrete steps; a value indicator when precision matters

      Use something else

      From Bootstrap: A plain .form-range gets the M3 Expressive track; the .slider wrapper adds sizes, ranges, ticks, icons and vertical sliders.

      Basic

      A Bootstrap range on its own: 16px track, a 4px bar handle that narrows while held, a gap around the handle and a stop dot. Chrome needs m3-components.js to draw the filled part; Firefox fills it either way.

      Slider component

      Wrap the range in .slider. The script draws the track from real geometry; the input stays underneath for keyboard, forms and screen readers. Without the script it falls back to the basic slider.

      Range

      Two inputs make a range slider. The handles can't cross.

      Sizes and icons

      An inset icon (sizes M and up) rides the start of the active track and hops past the handle when the active part gets too short. .slider-icon-start and .slider-icon-end pin icons to the track's ends.

      volume_mute volume_up
      volume_up
      light_mode
      tune

      Vertical

      Add .slider-vertical; it runs bottom to top. Give it a height (240px by default).

      volume_up
      Class / attribute on .sliderWhat it does
      .slider-s / -m / -l / -xlExpressive sizes: track 24 / 40 / 56 / 96px (default 16px).
      .slider-verticalVertical, bottom to top.
      .slider-labeledValue indicator above the handle while dragging or keyboard-focused.
      data-m3-suffix="%"Text appended to the value indicator.
      .slider-ticksA dot per step (needs step on the input).
      .slider-icon (child)Inset icon, sizes M and up.
      .slider-icon-start / -end (children)Icons fixed at the track's ends.

      If you set an input's value from code, call M3Components.refresh(input) afterwards; native inputs don't fire an event for that.

      Chips

      M3 chips

      Chips help people enter information, make selections, filter content or trigger actions, in a compact form.

      Use it for

      • Assist chips for smart, contextual actions
      • Filter chips to narrow down content
      • Input chips for values people entered, such as recipients

      Use something else

      • A main action: a button
      • A count or status label: a badge

      Assist and filter

      Assist chips are buttons. Filter chips use Bootstrap's .btn-check toggle pattern with a label.chip.

      Shape-morphing

      Add .chip-morph: corners go fully round when selected and tighten while pressed.

      Input chips

      .chip-input holds an entered value, with an optional .chip-avatar and a .chip-remove button. Removing is up to your code (the demo removes the chip on click).

      Ana Ruiz Bootstrap

      Dual list

      No M3 equivalentNeeds m3-components.js

      A dual list picks several items out of a long list by moving them from one side to the other, so what's chosen is always in plain view.

      Use it for

      • Choosing many from dozens or hundreds: team members, permissions, categories, columns to export
      • When people need to see and review everything they've chosen

      Use something else

      From Bootstrap: Bootstrap's multiple select is a list box you Ctrl-click in, where it's easy to lose a choice. M3 has no dual list, so this one is built from M3 parts: two lists on surface containers, a search field and tonal icon buttons. It stays a <select multiple> underneath, so the form posts it as always.

      Choose from a list

      Put data-m3-dual-list on a <select multiple>. Its unselected options are listed under Available, its selected ones under Selected. Click items to pick them (Shift-click for a range), then use the arrows, or double-click an item to move it straight across. Moved items fly to the other list on the spatial spring while both lists close up and make room. A search field filters each list once there are 8 or more options.

      Posts:

      Groups

      <optgroup>s become subheaders in both lists, and items keep to their group. Disabled options are shown but can't be moved.

      In order

      With data-reorder, the Selected list can be put in order: drag an item by its handle (the others spring out of its way), or pick items and use the move up and move down buttons, or press Alt+↑ / ↓. The options are reordered in the select, so the form posts them in that order. New arrivals go to the end, and an item taken out goes back to its place in Available. Groups only show in Available, since an order cuts across them.

      Posts:

      Keyboard: each list is a listbox. ↑ ↓ Home End move, Space picks, Shift+arrows pick a range, Ctrl+A picks all, and Enter moves the picked items (or the one you're on) to the other list. Moves are announced. required works: the Selected list is outlined in the error color and takes focus. Below 576px the lists stack and the arrows point down and up.

      Attribute or classWhat it does
      data-m3-dual-listOn a <select multiple>: shows it as a dual list. The select stays the form control and gets change events.
      data-available-label / data-selected-labelThe lists' titles (Available / Selected).
      data-search"true" or "false": always or never show the search fields (shown from 8 options otherwise).
      data-reorderThe Selected list can be put in order (drag handle, Alt+↑/↓, move up / down); the form posts it in that order.
      --dual-list-heightThe lists' height (360px).
      M3Components.dualList(select)Returns refresh() and move(values, toSelected); call refresh() after changing the options from code.

      Date pickers

      M3 date pickerNeeds m3-pickers.js

      Date pickers let people pick a date, or a range of dates, from a calendar or by typing it.

      Use it for

      • Picking a date that's likely near today, such as an appointment or a deadline
      • Dates where the day of the week matters

      Use something else

      • A date people know by heart, like a birthday: a plain text field (typing is faster than paging back decades)
      • A time of day: a time picker

      From Bootstrap: Bootstrap has no date picker, and a native type="date" field looks different in every browser. These are text fields with the M3 docked, modal and range calendars attached. The modal and range pickers use Bootstrap's modal.

      Docked, or a dialog on phones

      Add data-m3-datepicker to a text field. The script adds a calendar button and masks typing as MM/DD/YYYY. From 600px wide the calendar docks beneath the field (above it when there's no room); on a phone the button opens the dialog below instead, as M3 recommends. Add ="docked" to keep it docked everywhere. Choose a month or year from the menus in the header, or page with the arrows; OK fills the field, Cancel, Escape or a click outside leave it as it was.

      Limits and a starting value

      Set data-min and data-max as YYYY-MM-DD. Dates outside them are greyed out, the arrows stop at the edges, and a typed date outside the range fails validation. A value already in the field opens the calendar on that date.

      Modal

      On phones, or everywhere with data-m3-datepicker="modal", the calendar opens in a dialog: the chosen date as a large headline, a month row whose October 2026 button opens a grid of years, the days, and Cancel / OK. The pencil switches to typing the date instead (M3's modal date input); the field slides up as the calendar fades out.

      Date range

      Put two fields in a data-m3-daterange wrapper, marked data-m3-range="start" and "end". The calendar button opens M3's range picker: full screen on phones, a 568px dialog elsewhere, with every month in one scrolling list. Tap the start date, then the end; the days between get a secondary-container band. Save fills both fields. The pencil switches to typing both dates.

      Reading the value

      Keyboard: arrows move by day and week, Page Up and Page Down by month (with Shift, by year), Home and End to the start and end of the week, Enter picks, Escape closes. Alt+Down in the field opens the calendar.

      Attribute or classWhat it does
      data-m3-datepickerOn a text field: attach the date picker, docked from 600px wide and a dialog below.
      data-m3-datepicker="docked" / "modal"Docked on every screen size / the dialog on every screen size.
      data-m3-daterange + data-m3-range="start" / "end"A start and an end field sharing the range picker.
      data-min / data-maxEarliest and latest dates, as YYYY-MM-DD.
      input.m3DateThe chosen Date, or null.
      .datepickerThe calendar: 360×460, surface-container-high, large corners, level 3.
      .datepicker-dayA 40px day; today has a primary outline, the selected day is filled primary.

      Time pickers

      M3 time pickerNeeds m3-pickers.js

      Time pickers help people select and set a specific time.

      Use it for

      • Setting a time of day, such as an appointment, an alarm or a reminder

      Use something else

      From Bootstrap: Bootstrap has no time picker, and a native type="time" field looks different in every browser. This is a text field with M3's time picker dialog attached, using Bootstrap's modal.

      Dial

      Add data-m3-timepicker to a text field. The clock button opens the dialog: tap or drag around the dial to set the hour, and it moves on to the minutes by itself. Tap the hour or minute above the dial to go back to either. The keyboard button switches to typing the time instead. People can also type straight into the field: 930p, 9:30 pm and 21:30 all become 9:30 PM.

      24-hour clock

      Add data-24h. The dial gets an inner ring for 12 to 23, the AM/PM toggle goes away, and the field reads 21:30.

      Scroll wheels

      With data-m3-timepicker="scroll" the dialog shows two endless wheels instead of the dial (M3 Expressive's scroll time picker). Flick or drag them, or use the arrow keys; the number in the middle is the one chosen. Tap a number above or below to roll it into place. The keyboard button switches to typing, the same as on the dial. It works with data-24h too.

      Reading the value

      m3Time is always 24-hour HH:MM, the same as a native time field's value, so it can go straight to a server. Keyboard on the dial or a wheel: the arrow keys move by one, Page Up and Page Down by five. On the dial, Enter goes from the hour to the minutes, then confirms; on a wheel it confirms. Alt+Down in the field opens the dialog.

      Attribute or classWhat it does
      data-m3-timepickerOn a text field: attach the time picker.
      data-m3-timepicker="scroll"Scroll wheels instead of the dial.
      data-24hUse a 24-hour clock.
      input.m3TimeThe chosen time as "HH:MM", or null.
      .tp-fieldThe 100×120 hour and minute fields: surface-container-lowest, large corners, display-large. The active one is primary with a 2px outline.
      .tp-period-btnAM and PM: pills that square off to 12px corners in primary-container when chosen.
      .tp-dialThe 256px dial, with a primary hand and 48px handle.
      .tp-wheelA 100×120 scroll wheel of 66.7px rows. The middle number grows to display-large, emphasized, in on-surface; the rest are display-medium in outline.

      Color pickers

      No M3 equivalentNeeds m3-pickers.js

      Color pickers let people choose a color by eye, by its code, or from a set palette.

      Use it for

      • Brand or theme colors, where people often paste a hex code
      • Labeling things by color from a fixed set, with a palette-only picker

      Use something else

      From Bootstrap: Bootstrap's .form-control-color is the browser's own color input, which looks different everywhere and can't take a pasted code. Material has no color picker, so this one is built from M3 parts: the date picker's docked surface, the Expressive slider for hue, outlined fields, and swatches that change shape when chosen.

      Any color

      Add data-m3-colorpicker to a text field. The swatch button opens the picker: drag in the square for saturation and brightness, slide the hue, or type the color as hex or in RGB, HSL, HSB or OKLCH (the button group switches the three number fields). Changes show in the field as you go. From 600px wide the picker docks under the field: Escape puts the color back, and a click outside keeps it. On phones it opens as a dialog with Cancel and OK instead. Where the browser has one, the eyedropper button picks a color from anywhere on screen.

      Paste works in any form: #6750a4, 6750A4, #abc, rgb(103, 80, 164), 103, 80, 164, hsl(...), oklch(...) or a name like rebeccapurple. It turns into #RRGGBB straight away.

      With a palette

      Add data-palette with hex codes, separated by commas, to show them as swatches above the picker. Each is a circle that squares off with a check when it's the current color.

      Palette only

      With data-m3-colorpicker="palette" people can only choose from the palette: the picker is just the swatches (choosing one closes it; on phones, choose and then OK), and anything typed that isn't in the palette fails validation. Swatches keep their 8px spacing and wrap onto more rows, six to a row. Without data-palette it uses a built-in set of twelve.

      With transparency

      Colors are opaque unless you add data-alpha, which suits brand and theme colors. With it, the picker gets an opacity slider on a checkerboard and an A field (percent), and see-through colors are written as #RRGGBBAA, or with / 0.5 in the other formats. Pasted rgba(), #RGBA and / alpha values keep their opacity. Fully opaque colors stay six-digit.

      Other color formats

      The field holds hex by default. Set data-format to rgb, hsl or oklch to write the color that way instead; the picker then opens on that color space's fields. Out-of-range OKLCH colors keep their lightness and hue and lose chroma until they fit sRGB.

      Reading the value

      input fires as the color changes, change once it's settled (when the picker closes, or on blur after typing). Keyboard: arrows move the square's marker (Shift for bigger steps) and the hue; arrows move through swatches; Enter closes; Escape cancels. Alt+Down in the field opens the picker.

      Attribute or classWhat it does
      data-m3-colorpickerOn a text field: attach the color picker.
      data-palette="#hex, #hex"Swatches to offer.
      data-m3-colorpicker="palette"Only the palette's colors are allowed.
      data-format="rgb" / "hsl" / "oklch"Write the field in that format instead of hex.
      data-alphaAllow transparency: an opacity slider, an A field, #RRGGBBAA.
      input.m3ColorThe color as "#RRGGBB" ("#RRGGBBAA" when see-through with data-alpha), or null.
      .colorpickerThe 360px panel: surface-container-high, large corners, level 3. Below 600px it's inside a dialog.
      .cp-modesA connected button group switching the number fields between RGB, HSL, HSB and OKLCH.
      .cp-hueThe hue slider, built like M3's Expressive slider: a 16px track and a 4px handle with gaps either side.
      .cp-swatchA 40px swatch, 8px apart; circular, then a 12px rounded square with a check when chosen.

      Text inputs

      Text fields

      Bootstrap .form-controlM3 text fieldsJS for phone masking

      Text fields let people enter and edit text, in forms and dialogs.

      Use it for

      • Outlined fields for forms with many fields; filled fields to draw more attention
      • A visible label on every field; supporting text for hints, counts and errors

      Use something else

      From Bootstrap: .form-control, .form-select and .form-floating keep their markup and render as M3 outlined fields, with a notched floating label and M3 error colors.

      Outlined

      Supporting text sits under the field.

      Floating labels

      Bootstrap's .form-floating gives the M3 label that rests inside the field and floats onto the outline when focused or filled. It springs into place (fast-spatial, a slight overshoot).

      With m3-components.js, the outline gets a true notch behind the floated label, so the field works on any background. Without the script, the label hides the outline with its own background, which must match the surface behind the field. Cards, sheets and dialogs set this for you; elsewhere set --md-field-container to your background color.

      Filled

      Add .form-control-filled or .form-select-filled to the field, and .form-floating-filled to a floating wrapper.

      ZIP codes have 5 digits.

      Validation

      Bootstrap's validation classes work as usual (.is-invalid, .is-valid, or .was-validated on the form). Errors use the M3 error color on the outline, label and supporting text, with no status icons. Submit this form empty to see it:

      Please enter a name.
      Enter a valid email address.

      Character counter

      data-m3-counter adds M3's character count to the end of the supporting text line, with any helper text in a following .form-text kept on the left. With maxlength it's a hard limit: typing stops there. With data-limit it's a soft one, for drafting: you can keep going, the count and the field turn to the error color, and the form won't submit until it's back under. The count is part of the field's description for screen readers, and getting near the limit, reaching it and going over are announced. data-autosize lets a textarea grow with its text (up to 320px, then it scrolls).

      Shown on the project card.
      A soft limit that grows as you type.

      Setting a field's value from code doesn't fire input; call M3Components.refresh(field) afterwards to update its count. A browser sends a line break in a textarea as two characters (CR LF), but counts it as one, so allow for that on the server when checking length.

      Input groups

      Bootstrap input groups work and pick up the M3 field styling. M3 has no input group of its own.

      https://

      Phone numbers

      With m3-components.js, every <input type="tel"> formats itself as (XXX) XXX-XXXX while people type, paste or autofill. No extra markup. The cursor stays put while editing mid-number, and backspacing over (, ), the space or - removes the digit beside it. Pasted numbers like +1 915-555-1234 or 915.555.1234 are cleaned up, and a number starting with + and another country code is left as typed. The field gets a pattern (unless it has one), so validation flags an incomplete number.

      The formatted value is what the form submits. For the digits only, use input.value.replace(/\D/g, ''). Add data-m3-mask="none" to a field to leave it unformatted.

      Multi-select

      No M3 equivalentNeeds m3-components.js

      A multi-select field holds several choices as input chips, picked from a menu you can search by typing.

      Use it for

      • Several choices from a long list, in the space of one field: skills, tags, recipients, categories
      • Free-form tags, with suggestions

      Use something else

      • A handful of options, all visible: filter chips or checkboxes
      • Choosing dozens and reviewing them side by side: a dual list

      From Bootstrap: Bootstrap's multiple select is a box you Ctrl-click in. M3 has no multi-select, so this one (in the spirit of Select2) is built from M3 parts: the text field, input chips and the Expressive menu. It stays a <select multiple> underneath, so the form posts it as always and required works.

      Search and pick

      Put data-m3-multi-select on a <select multiple> with a <label>. What's chosen shows as input chips; type to filter the menu (the match is bolded), click or press Enter to pick, and the menu stays open for the next one. <optgroup>s are the menu's groups. Remove a chip with its , or press Backspace in an empty field.

      Filled, with pictures and a limit

      data-variant="filled" uses the filled text field. data-avatar on an option puts a picture in its chip and menu item, and data-max caps how many can be chosen (the menu says so when the cap is reached).

      Tags, in order

      data-create turns what's typed into a new option (Enter, or the "Add" item), like Select2's tags. data-reorder lets the chips be dragged into order (or moved with Alt+← / →); the options are reordered in the select, so the form posts them in that order.

      Posts:

      Without search

      data-search="false" makes it a plain picker: the field opens the menu, and Space or Enter picks.

      Keyboard: it follows the combobox pattern. ↓ / ↑ open the menu and move through it, Enter picks, Esc closes (and then clears the search). ← at the start of the field moves into the chips, ← / → move between them, and Delete removes one. Additions, removals and the number of matches are announced. The menu opens below the field, or above it when there isn't room.

      Attribute or classWhat it does
      data-m3-multi-selectOn a <select multiple>: shows it as a chip field over a menu. Its <label> (or data-label) is the floating label.
      data-variant="filled"The filled text field (outlined otherwise).
      data-placeholderShown in the empty field once it has focus.
      data-search="false"No typing: a plain picker.
      data-maxThe most that can be chosen.
      data-createTyped values become new options.
      data-reorderChips can be put in order; the form posts that order.
      data-avatarOn an <option>: a picture for its chip and menu item.
      M3Components.multiSelect(select)Returns refresh(), open(), close(), add(value) and remove(value).

      Location picker

      No M3 equivalentNeeds m3-components.js + a Google Maps key

      A location picker finds an address as you type, shows it on a map with a pin, and fills in the address and coordinates for the form.

      Use it for

      • Venues, offices, listings and anything else that's saved with its map position
      • Addresses where getting the coordinates right matters

      Use something else

      • A mailing address with no need for a map: plain text fields

      From Bootstrap: Neither Bootstrap nor M3 has one. This is built from M3 text fields, the Expressive menu for suggestions and a Google map tinted from the theme.

      Address, map and fields

      Put data-m3-location on an empty element, with your key in data-key and a form name in data-name. Type at least three characters and pick a suggestion: the fields fill in and the pin drops there. Drag the pin or click the map to fine-tune it, which updates the coordinates and leaves the address alone (data-reverse looks the address up as well). Typing coordinates moves the pin. data-value takes saved values as JSON, and the fields post as venue_data[address], venue_data[latitude] and so on. Without a key, the fields still work and the map says what's missing.

      Only what you need

      data-fields picks the fields, in order, from street_number, street_name, city, state, zip_code, country, latitude, longitude and building. This one is filled, has just the coordinates and is held above them with .location-picker-stacked.

      Try these with your key. It's saved in this browser only. Restrict a real key to your sites in Google's console, since it's visible in the page. Google refuses a key restricted to websites on a page opened from a file, so serve the page to test.

      In Google's console, enable Maps JavaScript API and Places API (New), plus Geocoding API for data-reverse. Keys on older projects that only have the legacy Places API work too: it falls back to that by itself. Maps loads only when the picker nears the screen or the address field gets focus, so a page that never shows it doesn't pay for a map load.

      Keyboard: ↓ / ↑ move through the suggestions, Enter picks and Esc closes. The number of suggestions and what's picked are announced. The coordinate fields are the keyboard way to place the pin.

      AttributeWhat it does
      data-m3-locationOn an empty element: builds the picker.
      data-keyThe Google Maps key (or set window.M3_GOOGLE_MAPS_KEY; not needed if the page already loads Maps).
      data-nameThe form name: fields post as name[address] etc.
      data-valueSaved values, as JSON.
      data-fieldsWhich fields, in order (a comma list).
      data-label / data-placeholderThe address field's label / placeholder.
      data-required / data-disabledThe address is required / everything is read-only.
      data-variant="filled"Filled text fields.
      data-center / data-zoomWhere the map starts with no location ("lat,lng", El Paso otherwise) / its zoom.
      data-types / data-countryLimit suggestions by Google place type / country code (comma lists).
      data-reverseMoving the pin looks up the address too.
      data-map-id / data-map-style="default"Your Google cloud map style / Google's own look, instead of the theme's tint.
      --location-map-heightThe map's height (334px).
      m3:locationEvent when the location changes; detail holds the values.
      M3Components.location(el)Returns value(), set(values), map() and marker().

      File upload

      No M3 equivalentNeeds m3-components.js + Dropzone

      File upload lets people add files by dropping them in, browsing for them, or pasting them.

      Use it for

      • Attachments on a form: documents, photos, a résumé
      • Uploads that start straight away, with progress, including very large files sent in chunks

      Use something else

      • One small image where the form looks plain: a regular form control with type="file"

      From Bootstrap: Bootstrap styles <input type="file"> as a text field with a browse button, and it can't take a drop or show what's chosen. Material has no file upload component, so this is Dropzone (v6) in M3 parts: a drop zone, Expressive segmented list rows and the linear progress indicator, in place of Dropzone's own stylesheet.

      Dropzone does the work: checking files, thumbnails, uploads, chunking big files and retrying chunks, resizing images before they're sent. The framework adds what it doesn't do: a zone you can reach and use from the keyboard, pasting a file, the transfer panel, a retry button for a failed upload, screen-reader announcements, and a plain-form mode. Without Dropzone on the page, the field stays an ordinary file input.

      Attach to a form

      Wrap a file input in .file-upload with data-m3-file-upload. Drop files on the zone, click it to browse, or paste one (a screenshot, say) while it has focus. Each file is listed with a thumbnail or type icon, its size, and a remove button. accept, data-max-size ("10MB") and data-max-files on the input become Dropzone's checks: anything that doesn't fit is listed in red with the reason. With no upload URL, the input always holds the accepted files, so the form posts them as normal, to PHP's $_FILES or anything else, and required works too.

      One file

      Without multiple, a new file replaces the one before.

      Upload as you go

      With data-upload-url, each file is sent as soon as it's added, three at a time, the way Stanton Admin's form builder works: the form itself then posts without files, and the server links what was uploaded to the submission. Progress shows on the file and in the transfer panel (data-transfer-panel="false" leaves the panel out). Removing a file cancels its upload, and a failed one gets a retry button. This demo fakes the server; files with "fail" in the name fail.

      For everything else, pass Dropzone's own options to M3Components.fileUpload; it rebuilds the uploader with them on top of the M3 defaults and returns the Dropzone. Large files in 1 MB chunks with retries, a CSRF token on every request, and files already on the server shown again after a failed submit:

      Tiles

      data-m3-file-upload="grid" shows the files as square tiles instead of rows, for photos and anything else people pick by how it looks. The zone spans the grid until a file is added, then becomes an "Add" tile after the last one, so the place to drop more stays beside the files. Uploads show a circular indicator over the tile; remove sits in the corner, always visible so it works on touch.

      Long names are cut in the middle, the way Finder does it (Quarterly-repo…final-v3.pdf), so the end that tells versions and types apart always shows. The full name is the tooltip and is what screen readers read.

      Color from the image. An image file lends its row or tile its own color, the content-based color M3 uses for media: its most vivid color fades into the surface behind the file, at most 30% of the mix, held where the text keeps its contrast on any image. Other files stay neutral. Tiles set the image into the card the way music apps do, with a bolder name (title medium, emphasized). data-image-color="false" on .file-upload turns it off.

      Previews

      Images and PDFs open from their row or tile: the whole file is the target, with a hover layer (and on a tile, a round primary button that rises into the image's corner, the way music apps do it, so the image itself is never covered), and a click or tap anywhere on it except its own buttons opens it. From the keyboard it's a button on the thumbnail (Enter or Space). An image opens in the media viewer, growing out of its thumbnail, with the uploader's other images in its strip; focus goes back to the thumbnail afterwards. A PDF opens in a new tab in the browser's own viewer. Word files, spreadsheets and the like can't be shown in a browser, so they have no preview.

      Compact

      data-m3-file-upload="button" swaps the zone for a tonal button, for tight spaces like a message composer. Dropping files anywhere on it and pasting still work.

      Dropzone's events all work on the returned instance (dz.on('success', …), 'error', 'queuecomplete', and so on). Keyboard: the zone is a button (Enter or Space browses), and each file's remove button is in the tab order. Changes are announced to screen readers.

      Attribute or classWhat it does
      .file-upload + data-m3-file-uploadAround an <input type="file">: the drop zone and file list (a Dropzone). ="button" for the compact version, ="grid" for tiles.
      accept, multiple, capture, requiredOn the input, as usual; they become Dropzone's checks.
      data-max-size / data-max-filesOn the input: largest file ("10MB") / most files.
      data-upload-urlUpload each file as it's added, as the input's name. Without it, the form posts the files.
      data-transfer-panel="false"Don't show uploads in the transfer panel.
      M3Components.fileUpload(el, options)The Dropzone; with options, rebuilt with any Dropzone options on top.
      .file-upload-zoneThe zone: surface-container-low, large corners, a dashed edge that turns primary while files are over it.
      data-m3-file-upload="grid"Files as tiles (.file-upload-tiles, .file-tile); the zone becomes an "Add" tile once there are files.
      .fu-name-start / .fu-name-endThe two halves of a middle-truncated name (the full name is the tooltip).
      .file-upload-openThe preview button over an image's or PDF's thumbnail (.is-previewable on the file).
      M3Components.imageViewer(items, index)The full-screen image viewer (.m3-viewer, a <dialog>). Items: { src, alt, title, width, height, thumb, opener }; returns { el, go(i), close() }.
      data-image-color="false"On .file-upload: don't tint files with their thumbnail's color.
      .has-image-colorSet on a file once its color is read; --md-image-color holds it, as with imagery.
      .file-upload-itemA file row (Dropzone's preview template, a segmented list item), with Dropzone's dz-processing, dz-success and dz-error states.

      JavaScript

      m3-components.js

      Optional helpers for what CSS can't do on its own. Load it after Bootstrap's bundle; it has no other dependencies and scans the page (including content added later) automatically.

      FeatureWhat it adds
      RipplesCompose's press ripple on buttons, chips, nav items, list and menu items, tabs.
      SlidersThe filled track on .form-range; the whole .slider component.
      TabsOne sliding indicator per .nav-tabs.
      Floating labelsA true notch in the outlined field's border.
      MenusClose animation; cascading submenus with hover intent and keyboard support.
      Navigation railExpand and collapse with data-m3-rail-toggle.
      FAB menu, expanding toolbarsOpen and close, Escape and outside click, focus handling.
      App bars, extended FABScrolled color, collapse on scroll, FAB collapse on scroll.
      Wavy progressSVG redraw each frame (round caps, eased amplitude).
      Snackbars, transfer panelM3Components.snackbar() and M3Components.transfer().

      API

      Data attributes

      Everything the scripts respond to in HTML.

      AttributeOnWhat it does
      data-m3-mode="light|dark|auto"buttonSwitches color mode (customizer).
      data-m3-rail-togglebutton in .nav-railExpands / collapses the rail.
      data-m3-app-bar.app-barScrolled color once the page scrolls.
      data-m3-app-bar="collapse"medium / large .app-barCollapses on scroll.
      data-m3-fab-collapse.btn-fab-extendedCollapses scrolling down, expands scrolling up.
      data-m3-snackbar (+ -action, -duration, -dismissible, -stacked)any clickable elementShows a snackbar.
      data-m3-suffix.sliderText after the value indicator's number.
      data-m3-ripple / data-m3-no-rippleany elementOpt in to / out of the ripple.

      m3-springs.js

      CSS transitions restart from zero speed when interrupted. Compose's springs keep their momentum, so a switch flipped back mid-slide curves smoothly into the new direction. This optional script integrates real springs for the switch handle and progress values to match.

      Spring constants come from the theme tokens, so the theme's springs and --md-motion-scale apply. Without the script everything still animates through CSS.

      Theme API

      The theme generator and customizer can be scripted, for example to build a client's theme from their brand color in your own tooling.

      M3Theme (m3-theme.js)

      M3Customizer (m3-customizer.js)

      Generators

      Node scripts used to build parts of the framework from Compose's source. You only need them to change those parts; the outputs are already in the repo.

      ScriptBuilds
      tools/shapes-gen.jsThe loading indicator's shape morphs (tools/shapes.json), ported from graphics-shapes. Run node tools/shapes-gen.js 160 tools/shapes.json.
      tools/li-css.jsThe loading indicator CSS block in m3-bootstrap.css. Run node tools/li-css.js .
      tools/material-shapes.jscss/m3-shapes.css and assets/shapes/*.svg. Run node tools/material-shapes.js.