/* * Button groups and split buttons: shapes that depend on a button's place among its siblings. * * Unlayered on purpose. Each draws its own corners and padding as utilities, and a * rule in any @layer loses to a utility whatever its specificity; the group has to win. * * Standard group (``): pressing a label button widens it and narrows its * neighbours by the same amount, so the row keeps its length — Expressive's press expansion * (ButtonGroupDefaults.ExpandedRatio is 15%; this uses a fixed step per size). Icon buttons keep * their width. * * Connected group (``, ``): 2px apart, the outer corners * full, the inner corners small, smaller still while pressed, and a selected segment fully round * (ConnectedButtonGroupSmallTokens and the M3 Expressive connected button group spec). * * Shape (`shape="square"`, M3's "Default shape | Round, square" configuration): `--group-outer` * is what the two ends of a group round to — half the height for a round group, and for a square * one the same corner its inner edges take, which is the square table M3 publishes for connected * groups (4/8/8/16/20dp by size). A square *standard* group has no inner edges, so it hands its * buttons the square corner scale `` draws (`--group-square`, and * `--group-square-pressed` under the finger) instead of asking the caller to write it on each * one. Selection morphs the other way in both: M3 has a toggle inside a group "swap shape * square/round on selection", so a selected button in a square group turns round, which is why * the selected rules keep `--group-full` rather than `--group-outer`. * * Split button (``): the same idea for two halves, on its own corner scale — * SplitButton*Tokens gives the inner corner 4/4/4/8/12px at rest and *grows* it to 8/12/12/20/20px * under the finger, the opposite of a connected group, so `--split-inner*` is separate from * `--group-inner*`. The trailing half turns round and its chevron turns over while its menu is * open, on M3's standard scheme rather than the expressive springs (the site: "the trailing * button uses the standard motion scheme, not expressive, when rotating"), and the chevron sits * a little towards the leading half so the pair reads as one control (`--split-nudge`: * 1/1/2/3/6px). `--split-icon` is the trailing icon's own size, 22/22/26/38/50px, which the * icon-button table does not know about. * * "Full" here is half the size's height (`--group-full`), never `--md-sys-shape-corner-full`'s * 9999px. One element mixes full outer corners with small inner ones, and when a box's radii * add up to more than its side, CSS scales every radius by the same factor: a 9999px corner * beside an 8px one shrank the 8px one to a hundredth of a pixel, so the inner corners drew * square. */ [data-button-group] { --group-inner: var(--md-sys-shape-corner-sm); --group-inner-pressed: var(--md-sys-shape-corner-xs); --group-full: 1.25rem; --group-square: var(--md-sys-shape-corner-md); --group-square-pressed: var(--md-sys-shape-corner-sm); --group-outer: var(--group-full); } [data-button-group][data-size='xs'] { --group-pad: 1rem; --group-grow: 4px; --group-inner: var(--md-sys-shape-corner-xs); --group-inner-pressed: 2px; --group-full: 1rem; } [data-button-group][data-size='sm'] { --group-pad: 1rem; --group-grow: 6px; --group-full: 1.25rem; } [data-button-group][data-size='md'] { --group-pad: 1.5rem; --group-grow: 8px; --group-full: 1.75rem; --group-square: var(--md-sys-shape-corner-lg); --group-square-pressed: var(--md-sys-shape-corner-md); } [data-button-group][data-size='lg'] { --group-pad: 3rem; --group-grow: 16px; --group-inner: var(--md-sys-shape-corner-lg); --group-inner-pressed: var(--md-sys-shape-corner-md); --group-full: 3rem; --group-square: var(--md-sys-shape-corner-xl); --group-square-pressed: var(--md-sys-shape-corner-lg); } [data-button-group][data-size='xl'] { --group-pad: 4rem; --group-grow: 20px; --group-inner: var(--md-sys-shape-corner-lg-increased); --group-inner-pressed: var(--md-sys-shape-corner-lg); --group-full: 4.25rem; --group-square: var(--md-sys-shape-corner-xl); --group-square-pressed: var(--md-sys-shape-corner-lg); } /* A square group's ends take the corner its inner edges take — M3's square table for connected groups — and a square standard group hands its buttons the button's own square scale. */ [data-button-group][data-shape='square'] { --group-outer: var(--group-inner); } [data-button-group='standard'][data-shape='square'] > * { border-radius: var(--group-square); } [data-button-group='standard'][data-shape='square'] > :active:not(:disabled, [aria-disabled='true']) { border-radius: var(--group-square-pressed); } [data-button-group='standard'][data-shape='square'] > [aria-pressed='true'] { border-radius: var(--group-full); } [data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) { padding-inline: calc(var(--group-pad) + var(--group-grow)); } [data-button-group='standard'] > :not([data-icon-button]):has(+ :not([data-icon-button]):active:not(:disabled, [aria-disabled='true'])) { padding-inline-end: calc(var(--group-pad) - var(--group-grow)); } [data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) + :not([data-icon-button]) { padding-inline-start: calc(var(--group-pad) - var(--group-grow)); } [data-button-group='split'] { --split-inner: var(--md-sys-shape-corner-xs); --split-inner-pressed: var(--md-sys-shape-corner-md); --split-icon: 22px; --split-nudge: 1px; } [data-button-group='split'][data-size='xs'] { --split-inner-pressed: var(--md-sys-shape-corner-sm); } [data-button-group='split'][data-size='md'] { --split-icon: 26px; --split-nudge: 2px; } [data-button-group='split'][data-size='lg'] { --split-inner: var(--md-sys-shape-corner-sm); --split-inner-pressed: var(--md-sys-shape-corner-lg-increased); --split-icon: 38px; --split-nudge: 3px; } [data-button-group='split'][data-size='xl'] { --split-inner: var(--md-sys-shape-corner-md); --split-inner-pressed: var(--md-sys-shape-corner-lg-increased); --split-icon: 50px; --split-nudge: 6px; } /* * A connected segment at the two smallest sizes is 32px or 40px tall, and an icon-only one is as * narrow as it is tall. M3 names both numbers and tells you not to reduce them: "XS and S * connected button groups have a 48dp target area and a 48dp minimum width". The target is the * `touch-target` utility on the segment itself; the width is here, where a group can reach the * ``s a caller put inside it. */ [data-button-group='connected'][data-size='xs'] > *, [data-button-group='connected'][data-size='sm'] > * { min-inline-size: 3rem; } /* * Connected segments and split halves take their inner corner from `--group-corner`, so pressing * or selecting changes one variable and the rounded outer corners stay put. */ [data-button-group='connected'] > *, [data-split] { border-start-start-radius: var(--group-corner); border-end-start-radius: var(--group-corner); border-start-end-radius: var(--group-corner); border-end-end-radius: var(--group-corner); } [data-button-group='connected'] > * { --group-corner: var(--group-inner); } [data-button-group='connected'] > :active { --group-corner: var(--group-inner-pressed); } [data-split] { --group-corner: var(--split-inner); } [data-split]:active { --group-corner: var(--split-inner-pressed); } [data-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)), [data-split='trailing'][aria-expanded='true'] { --group-corner: var(--group-full); } [data-button-group='connected'] > :first-child, [data-split='leading'] { border-start-start-radius: var(--group-outer); border-end-start-radius: var(--group-outer); } [data-button-group='connected'] > :last-child, [data-split='trailing'] { border-start-end-radius: var(--group-outer); border-end-end-radius: var(--group-outer); } /* * The trailing chevron: its own size, and `--split-nudge` off centre towards the leading half — * equal and opposite margins move it by exactly that much inside a centred flex row, in whichever * direction the writing mode runs. */ [data-split='trailing'] svg { width: var(--split-icon); height: var(--split-icon); margin-inline-start: calc(-1 * var(--split-nudge)); margin-inline-end: var(--split-nudge); transition: rotate var(--md-sys-motion-duration-short) var(--md-sys-motion-easing-standard); } [data-split='trailing'][aria-expanded='true'] svg { rotate: 180deg; }