/* * : M3 Expressive's buttons — label button, icon button and toggle — and the page's * create action that is a FAB on a compact window. * * Geometry from androidx Compose Material 3's tokens (Apache-2.0), per `data-md-size`: * * size height label padding gap type icon (label / icon-only) outline square pressed * xs 32px 16px 8px label-lg 20 / 20px 1px md sm * sm 40px 16px 8px label-lg 20 / 24px 1px md sm * md 56px 24px 8px title-md 24 / 24px 1px lg md * lg 96px 48px 12px headline-sm 32 / 32px 2px xl lg * xl 136px 64px 16px headline-lg 40 / 40px 3px xl lg * * (ButtonXSmall…XLargeTokens; the xs padding is the 16px Phase D settled on, ACT-09.) An icon button * (`data-md-icon-button`) is as tall and `narrow`, `default` or `wide` across by * XSmall…XLargeIconButtonTokens: 28/32/40, 32/40/52, 48/56/72, 64/96/128 and 104/136/184px. The * views size the glyphs through ``; `--md-button-icon` carries the same size for the * loading indicator that stands in for the glyph while the button's action runs. * * Shape: round (`--md-sys-shape-corner-full`) by default; `data-md-shape="square"` and a selected * button take the size's square corner, a selected square icon button turns round, and every one * squares off to the pressed corner under the finger. Those corner rules are wrapped in `:where()`, * so they carry no specificity at all: a group's stylesheet (button-group.css, split-button.css) * reshapes the buttons inside it with any selector, without a specificity fight. * * Colour: `data-md-color` picks the roles a variant draws with; the variants set * `--md-button-container`, `--md-button-label`, `--md-button-outline` and `--md-button-elevation`, * which one rule paints, so the disabled treatment (on-surface at 10% behind 38% text, * FilledButtonTokens.DisabledContainerOpacity and DisabledLabelTextOpacity) replaces them with a * plain declaration. Primary's tonal button is secondary-container and its standard icon button * on-surface-variant, as M3 draws them; the text button's label is primary, as Compose draws it, * not the token's on-surface-variant, which its own source marks as wrong; a selected text button * takes the tonal container (the library's deliberate deviations, docs/audits/m3-alignment). * * Motion: corners, padding, margins and `bottom` on the fast spatial spring, colour and shadow on * the fast effects spring beside it — a colour must never overshoot (tokens/motion.css). * * The button renders the foundation's `md-state-layer` and `md-focus-ring` * (foundation/interaction.css), and, at `xs` and `sm` where it draws under 48px, `md-touch-target` * too; nothing here copies their rules. * * `data-md-compact-fab`: below `medium` (600px) the button is an extended FAB in the thumb zone — * fixed 16px from the end, 56px tall with 16px padding, the large corner, `title-md`, * primary-container at elevation 3 (ExtendedFabPrimaryTokens) — lifted clear of a bottom bar and * of a snackbar on screen (`--material-bottom-bar`, `--material-snackbar-height`), because M3 puts a * snackbar above a FAB and never over one. From `medium` it is the button it was written as. * `data-md-responsive` on the label hides it below `expanded` (840px). */ @layer material.reset, material.tokens, material.base, material.layout, material.components, material.text, material.visibility; @import './icon.css'; @import './loading.css'; @import './tooltip.css'; @layer material.components { [data-md-button] { --md-button-color: var(--md-sys-color-primary); --md-button-on-color: var(--md-sys-color-on-primary); --md-button-tone: var(--md-sys-color-secondary-container); --md-button-on-tone: var(--md-sys-color-on-secondary-container); --md-button-tone-selected: var(--md-sys-color-secondary); --md-button-on-tone-selected: var(--md-sys-color-on-secondary); --md-button-quiet: var(--md-sys-color-on-surface-variant); --md-button-container: transparent; --md-button-label: var(--md-button-color); --md-button-outline: transparent; --md-button-elevation: none; display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center; block-size: var(--md-button-height); background-color: var(--md-button-container); color: var(--md-button-label); box-shadow: var(--md-button-elevation); white-space: nowrap; cursor: pointer; -webkit-user-select: none; user-select: none; transition-property: border-radius, padding, margin, bottom, background-color, color, box-shadow; transition-duration: var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-spatial-fast-duration), var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-effects-fast-duration), var(--md-sys-motion-effects-fast-duration); transition-timing-function: var(--md-sys-motion-spatial-fast), var(--md-sys-motion-spatial-fast), var(--md-sys-motion-spatial-fast), var(--md-sys-motion-spatial-fast), var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast), var(--md-sys-motion-effects-fast); } /* Sizes. */ [data-md-button][data-md-size='xs'] { --md-button-height: 32px; --md-button-padding: var(--md-sys-measurement-space200); --md-button-gap: var(--md-sys-measurement-space100); --md-button-icon: 20px; --md-button-outline-width: 1px; --md-button-square: var(--md-sys-shape-corner-md); --md-button-pressed: var(--md-sys-shape-corner-sm); --md-button-narrow: 28px; --md-button-default: 32px; --md-button-wide: 40px; } [data-md-button][data-md-size='sm'] { --md-button-height: 40px; --md-button-padding: var(--md-sys-measurement-space200); --md-button-gap: var(--md-sys-measurement-space100); --md-button-icon: 20px; --md-button-outline-width: 1px; --md-button-square: var(--md-sys-shape-corner-md); --md-button-pressed: var(--md-sys-shape-corner-sm); --md-button-narrow: 32px; --md-button-default: 40px; --md-button-wide: 52px; } [data-md-button][data-md-size='sm'][data-md-icon-button] { --md-button-icon: 24px; } [data-md-button][data-md-size='md'] { --md-button-height: 56px; --md-button-padding: var(--md-sys-measurement-space300); --md-button-gap: var(--md-sys-measurement-space100); --md-button-icon: 24px; --md-button-outline-width: 1px; --md-button-square: var(--md-sys-shape-corner-lg); --md-button-pressed: var(--md-sys-shape-corner-md); --md-button-narrow: 48px; --md-button-default: 56px; --md-button-wide: 72px; } [data-md-button][data-md-size='lg'] { --md-button-height: 96px; --md-button-padding: var(--md-sys-measurement-space600); --md-button-gap: 12px; --md-button-icon: 32px; --md-button-outline-width: 2px; --md-button-square: var(--md-sys-shape-corner-xl); --md-button-pressed: var(--md-sys-shape-corner-lg); --md-button-narrow: 64px; --md-button-default: 96px; --md-button-wide: 128px; } [data-md-button][data-md-size='xl'] { --md-button-height: 136px; --md-button-padding: var(--md-sys-measurement-space800); --md-button-gap: var(--md-sys-measurement-space200); --md-button-icon: 40px; --md-button-outline-width: 3px; --md-button-square: var(--md-sys-shape-corner-xl); --md-button-pressed: var(--md-sys-shape-corner-lg); --md-button-narrow: 104px; --md-button-default: 136px; --md-button-wide: 184px; } [data-md-button]:not([data-md-icon-button]) { gap: var(--md-button-gap); padding-inline: var(--md-button-padding); } [data-md-button]:not([data-md-icon-button]):is([data-md-size='xs'], [data-md-size='sm']) { font: var(--md-sys-typescale-label-lg); letter-spacing: var(--md-sys-typescale-label-lg-tracking); font-variation-settings: normal; } [data-md-button]:not([data-md-icon-button])[data-md-size='md'] { font: var(--md-sys-typescale-title-md); letter-spacing: var(--md-sys-typescale-title-md-tracking); font-variation-settings: normal; } [data-md-button]:not([data-md-icon-button])[data-md-size='lg'] { font: var(--md-sys-typescale-headline-sm); letter-spacing: var(--md-sys-typescale-headline-sm-tracking); font-variation-settings: normal; } [data-md-button]:not([data-md-icon-button])[data-md-size='xl'] { font: var(--md-sys-typescale-headline-lg); letter-spacing: var(--md-sys-typescale-headline-lg-tracking); font-variation-settings: normal; } [data-md-button][data-md-icon-button] { --md-button-width: var(--md-button-default); inline-size: var(--md-button-width); &[data-md-width='narrow'] { --md-button-width: var(--md-button-narrow); } &[data-md-width='wide'] { --md-button-width: var(--md-button-wide); } } /* Shape, without specificity: a group reshapes the buttons in it. */ :where([data-md-button]) { border-radius: var(--md-sys-shape-corner-full); } :where([data-md-button]):where([data-md-shape='square'], [data-md-selected='true']) { border-radius: var(--md-button-square); } :where([data-md-button]):where([data-md-icon-button][data-md-shape='square'][data-md-selected='true']) { border-radius: var(--md-sys-shape-corner-full); } :where([data-md-button]):where(:active) { border-radius: var(--md-button-pressed); } /* Colour roles. */ [data-md-button][data-md-color='secondary'] { --md-button-color: var(--md-sys-color-secondary); --md-button-on-color: var(--md-sys-color-on-secondary); --md-button-tone: var(--md-sys-color-secondary-container); --md-button-on-tone: var(--md-sys-color-on-secondary-container); } [data-md-button][data-md-color='tertiary'] { --md-button-color: var(--md-sys-color-tertiary); --md-button-on-color: var(--md-sys-color-on-tertiary); --md-button-tone: var(--md-sys-color-tertiary-container); --md-button-on-tone: var(--md-sys-color-on-tertiary-container); } [data-md-button][data-md-color='error'] { --md-button-color: var(--md-sys-color-error); --md-button-on-color: var(--md-sys-color-on-error); --md-button-tone: var(--md-sys-color-error-container); --md-button-on-tone: var(--md-sys-color-on-error-container); } [data-md-button][data-md-color='success'] { --md-button-color: var(--md-sys-color-success); --md-button-on-color: var(--md-sys-color-on-success); --md-button-tone: var(--md-sys-color-success-container); --md-button-on-tone: var(--md-sys-color-on-success-container); } [data-md-button][data-md-color='warning'] { --md-button-color: var(--md-sys-color-warning); --md-button-on-color: var(--md-sys-color-on-warning); --md-button-tone: var(--md-sys-color-warning-container); --md-button-on-tone: var(--md-sys-color-on-warning-container); } [data-md-button][data-md-color='info'] { --md-button-color: var(--md-sys-color-info); --md-button-on-color: var(--md-sys-color-on-info); --md-button-tone: var(--md-sys-color-info-container); --md-button-on-tone: var(--md-sys-color-on-info-container); } /* Every colour but primary selects into the colour itself and is quiet in it. */ [data-md-button]:not([data-md-color='primary']) { --md-button-tone-selected: var(--md-button-color); --md-button-on-tone-selected: var(--md-button-on-color); --md-button-quiet: var(--md-button-color); } /* Variants. */ [data-md-button][data-md-variant='filled'] { --md-button-container: var(--md-button-color); --md-button-label: var(--md-button-on-color); &[data-md-selected='false'] { --md-button-container: var(--md-sys-color-surface-container); --md-button-label: var(--md-sys-color-on-surface-variant); } } [data-md-button][data-md-variant='tonal'] { --md-button-container: var(--md-button-tone); --md-button-label: var(--md-button-on-tone); &[data-md-selected='true'] { --md-button-container: var(--md-button-tone-selected); --md-button-label: var(--md-button-on-tone-selected); } } [data-md-button][data-md-variant='outlined'] { --md-button-label: var(--md-button-quiet); --md-button-outline: var(--md-sys-color-outline-variant); border: var(--md-button-outline-width) solid var(--md-button-outline); &[data-md-selected='true'] { --md-button-container: var(--md-sys-color-inverse-surface); --md-button-label: var(--md-sys-color-inverse-on-surface); --md-button-outline: transparent; } } [data-md-button][data-md-variant='elevated'] { --md-button-container: var(--md-sys-color-surface-container-low); --md-button-elevation: var(--md-sys-elevation-1); &[data-md-selected='true'] { --md-button-container: var(--md-button-color); --md-button-label: var(--md-button-on-color); } } [data-md-button][data-md-variant='text'][data-md-icon-button]:not([data-md-selected='true']) { --md-button-label: var(--md-button-quiet); } [data-md-button][data-md-variant='text']:not([data-md-icon-button])[data-md-selected='true'] { --md-button-container: var(--md-button-tone); --md-button-label: var(--md-button-on-tone); } @media (hover: hover) { [data-md-button]:is([data-md-variant='filled'], [data-md-variant='tonal']):hover { --md-button-elevation: var(--md-sys-elevation-1); } [data-md-button][data-md-variant='elevated']:hover { --md-button-elevation: var(--md-sys-elevation-2); } } /* Disabled: no state layer, no shadow, M3's disabled colours over whatever the variant set. */ [data-md-button]:is(:disabled, [aria-disabled='true']) { box-shadow: none; color: color-mix(in srgb, var(--md-sys-color-on-surface) calc(var(--md-sys-state-disabled-content-opacity) * 100%), transparent); } [data-md-button]:is([data-md-variant='filled'], [data-md-variant='tonal'], [data-md-variant='elevated'], [data-md-variant='outlined'][data-md-selected='true']):is(:disabled, [aria-disabled='true']) { background-color: color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent); } [data-md-button]:disabled { cursor: not-allowed; } [data-md-button][aria-disabled='true'] { pointer-events: none; } /* The glyph's place, and the loading indicator that stands in for it. */ [data-md-button-icon] { display: contents; } [data-md-button-spinner] { align-items: center; justify-content: center; } [data-md-button-spinner] > [data-md-loading] { inline-size: var(--md-button-icon); block-size: var(--md-button-icon); color: currentColor; } @media (width < 840px) { [data-md-button] > [data-md-responsive] { display: none; } } @media (width < 600px) { [data-md-button][data-md-compact-fab] { --md-button-height: 56px; --md-button-padding: var(--md-sys-measurement-space200); --md-button-gap: var(--md-sys-measurement-space100); position: fixed; inset-inline-end: var(--md-sys-measurement-space200); bottom: calc(var(--material-bottom-bar, 0px) + var(--material-snackbar-height, 0px) + var(--md-sys-measurement-space200)); z-index: 30; border-radius: var(--md-sys-shape-corner-lg); background-color: var(--md-sys-color-primary-container); color: var(--md-sys-color-on-primary-container); box-shadow: var(--md-sys-elevation-3); font: var(--md-sys-typescale-title-md); letter-spacing: var(--md-sys-typescale-title-md-tracking); font-variation-settings: normal; } [data-md-button][data-md-compact-fab] [data-md-icon] { inline-size: 24px; block-size: 24px; } } }