[material-ui] CSS-var density adapter experiment#48624
Draft
siriwatknp wants to merge 25 commits into
Draft
Conversation
Expose Button padding as overridable CSS vars resolved inline from a (variant,size) lookup; add enhanceDensity to wire tokens to a --mui-density-* scale. Literal-px fallbacks keep the default pixel-identical. Design in CONTEXT.md + docs/adr/0001; demo at /experiments/density-tokens.
Deploy previewhttps://deploy-preview-48624--material-ui.netlify.app/ Bundle size
Check out the code infra dashboard for more information about this PR. |
…t of density experiment
… + --Button-pad seam - Root consumes var(--Button-pad, var(--_pad)); --_pad universal default on root - (variant,size) literals + built-in-size routing live in variants (deduped CSS) - Inline bridge only for custom sizes (keeps custom sizes tunable, zero inline for built-ins) - Two-var rationale + accepted trade-offs documented in ADR-0001 + CONTEXT - enhanceDensity maps sized tokens (--Button-<size>-pad) to density scale
…seam - OutlinedInput: block-only density (--OutlinedInput-<size>-padBlock); root routes, input inherits; drop redundant size/multiline variants - InputLabel: generic --InputLabel-y seam; OutlinedInput bridges sibling label via :has(~ &) - Docs: ADR-0001 OutlinedInput + label :has bridge, CONTEXT, density-adapter-rollout guide, experiment demo
- density-fixture.tsx: per-component matrix scoped by ?c=&level (default pixel-identical) - scripts/density-screenshots: config + spec + README (maxDiffPixels 0) - density:shot / density:shot:update scripts; gitignore harness outputs
- Tokenize the 14px inline gutter as --OutlinedInput-padInline (size-invariant base token) - Uniform consume shape var(--seam, var(--_internal)) across both axes: block sized (routed), inline base; --_padInline internal default - Docs: base-token shape in ADR/CONTEXT; rollout gotchas — split-only-if-forced, uniform consume shape, inline gutter != adornment gap
Revert the lift of block padding to the root + inheritance; tokenize each literal where master has it (input owns inline/non-multiline, root owns multiline/adornment gutters) for the smallest diff. Promote padInline from a base token to a sized axis: default 14px both sizes, but expose --OutlinedInput-<size>-padInline so a design system can tune inline density per size. Both axes now routed per size in place; label :has derives --InputLabel-y straight from the public sized token. Docs: base token reserved for axes where per-size override is meaningless; a size-invariant default alone no longer justifies it.
Apply the density adapter (docs/adr/0001) to the @mui/material components used by the dashboard template: Chip, IconButton, MenuItem, ListItem, ListItemButton, ListItemIcon, ListItemText, ListSubheader, Toolbar, Tab, Tabs, TablePagination, CardContent, Select, Breadcrumbs, InputAdornment, Badge. Each exposes its real spacing axes as public sized tokens over literal-px internal defaults; the default render stays pixel-identical to master (density screenshot harness, maxDiffPixels:0). Checkbox/FormControl skipped - no density axis. enhanceDensity wires every component's sized tokens (incl. OutlinedInput) to the density scale. The verification fixture gains a matrix + dense/loose scope per component. Boolean `dense` components (MenuItem, ListItem, ListItemButton, ListItemText) expose the default state via the plain seam --Component-<key> and only the dense override as --Component-dense-<key>. Toolbar keeps theme.mixins.toolbar for its regular height (only dense + gutters tokenized).
Boolean compactness toggles (dense) use a state token: default state is the plain seam --Component-<key> (base-token-shaped, no base routing), only the on state is qualified --Component-dense-<key>. No --Component-normal/regular/default- qualifier - a boolean has no name for off. Added to CONTEXT language, ADR 0001 resolution, and the rollout recipe + naming.
SwitchBase (shared agnostic base) consumes one seam: padding var(--SwitchBase-pad, var(--_pad)), --_pad 9px. Checkbox/Radio (styled(SwitchBase)) route per-size public tokens --Checkbox/Radio-<size>-pad into the seam; default 9px both sizes (pixel-identical). Switch routes its thumb (SwitchBase) padding via --Switch-<size>-pad (9/4); box geometry stays literal (size-coupled). enhanceDensity + fixture wired.
SwitchBase owns the agnostic seam consumed once; Checkbox/Radio/Switch route per-component sized tokens into it. Covers the two reader topologies (consumer is the base vs wraps it as a descendant), delivery via custom-property inheritance (no descendant selector), and the --_<key>-shadowing caveat. Added to CONTEXT relationships, ADR 0001 specifics, rollout Recipe C + Done list.
Tokenize Switch's four real dims per size (--Switch-<size>-width/height/thumbSize/ touchSize). Derive SwitchBase pad = (touchSize-thumbSize)/2, button top = (height-touchSize)/2, checked travel = width-touchSize, thumb size = thumbSize, so the thumb stays centered on the track (absolute + transform). Replaces the pad-only token that drifted the thumb. Switch dropped from enhanceDensity (geometry isn't spacing-scale-derived). Default pixel-identical.
…lues Switch tokenizes width/height/thumbSize/touchSize per size and derives pad/top/ travel via calc (thumb stays centered); not the pad-only approach. Corrects the shared-base sections in ADR 0001 + rollout Recipe C.
The root padding (12/7, track inset) is its own axis -> tokenize as --Switch-<size>-pad over --_pad, consumed padding: var(--Switch-pad, var(--_pad)). Distinct from the derived thumb SwitchBase pad. Fixture scope + docs updated.
borderRadius = (height - 2*pad)/2 (full-pill track thickness) instead of literal 14/2, so the track stays rounded when the dims are tuned. Pixel-identical (medium 7px; small clamps to a pill).
Add an xxl density step (4x spacing unit). Wire MuiSwitch: map per-size width/height/touchSize/thumbSize/pad to scale steps (xxl for the wider track); pad/top/travel/radius re-derive so the geometry stays valid. Docs updated.
Switch dims were mapped to single scale steps, shrinking it. Compose from steps so defaults land on today's px (medium 58/38/20/38/12, small 40/24/16/24/7) and still scale with density: width calc(xxl*2-6), height/touch calc(xxl+xs), thumb calc(lg+xxs), etc. touchSize == height keeps the thumb centered.
…Tabs minHeight - enhanceDensity: derive OutlinedInput --InputLabel-y from density step (sibling label can't read the input's padBlock token); per-size via variants - MenuItem: consume --ListItemIcon-minWidth (was hardcoded 36) so density reaches the icon - Tabs: add --Tabs-minHeight base seam (parent can't read child --Tab-minHeight) + wire MuiTabs
- New /experiments/density-showcase: preset switcher (compact/normal/comfort), live scale readout + per-component token accordion, masonry gallery - Extract shared demos to densityDemos.tsx; fixture imports it - Fixture: --Tabs-minHeight scope, center row Stacks
calc(var(--Chip-height) - inset) per size so they track density; insets reproduce today's medium/small sizes (pixel-identical default)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Preview: https://deploy-preview-48624--material-ui.netlify.app/experiments/density-showcase/
The idea
Let designers tune component density — per component, per size, or holistically across the app — without editing component source, writing
calc, or accepting that everything reflows off one global--mui-spacingdial.This is a third take on density (sibling of
feat/components-theme-spacingandpoc/css-vars-map): a CSS-variable adapter. Every density-bearing dimension becomes one hand-authorable CSS variable, with a literal-px fallback so the un-configured theme renders today's exact pixels (Argos zero-diff). Nothing breaks; you opt in only where you want to.The approach
Each component is read as three layers of responsibility, sharing one cascade:
The styled root consumes the seam, which falls back to the internal default — one consumption point per property, no JS branching on size/variant in the styles:
So three audiences each get the layer they care about: an agnostic root with no design opinion, the Material sizes/variants on top, and a design-system override surface through the public tokens — all without touching the others.
Holistic density
One opt-in function (mirroring
enhanceHighContrast) turns the per-component tokens into an app-wide density scale. It both emits a--mui-density-*scale and maps each component's sized tokens onto it.createThemeis left untouched:The showcase wires this to three presets — Compact / Normal / Comfort — where flipping one switch reflows the whole component gallery.
Normalis pixel-identical to today.A few shapes the model handles
denseprop uses a state token (only the on-state is named). A size-invariant base token is reserved for the rare axis where per-size override is meaningless.TextField's floatingInputLabelis a preceding sibling of the input; OutlinedInput owns a:hasbridge so one padding knob moves the input box and its label together.SwitchBase's seam. A Switch's width/height/thumb/touch/travel all move together, so it tokenizes the real dims and derives the coupled values withcalc— the thumb stays centered at any density.Scope
Button · OutlinedInput (+ InputLabel, TextField outlined) · the dashboard set (Chip, IconButton, MenuItem, ListItem/Button/Icon/Text, ListSubheader, Toolbar, Tab/Tabs, TablePagination, CardContent, Select, Breadcrumbs, InputAdornment, Badge) · the SwitchBase family (Checkbox, Radio, Switch).
Try it
/experiments/density-showcase/experiments/density-tokensDesign notes
docs/adr/0001-css-var-density-adapter.mddocs/adr/density-adapter-rollout.mdNot intended to merge as-is — opened for the deploy preview and review.