actual-css 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -3
- package/README.md +2 -2
- package/dist/actual.css +54 -14
- package/dist/actual.full.css +744 -185
- package/dist/actual.full.js +1 -1
- package/dist/actual.full.min.css +1 -1
- package/dist/actual.min.css +1 -1
- package/docs/design-notes/browser-support.md +14 -14
- package/docs/design-notes/enhancement-contract.md +30 -13
- package/docs/design-notes/forced-colors.md +11 -7
- package/docs/design-notes/grid.md +4 -4
- package/docs/design-notes/naming.md +15 -9
- package/docs/design-notes/platform-alignment.md +9 -0
- package/docs/design-notes/references.md +4 -0
- package/docs/design-notes/spacing-units.md +286 -0
- package/docs/design-notes/steps.md +748 -0
- package/docs/design-notes/widget-primitives.md +32 -15
- package/docs/navigation.json +2 -0
- package/docs/pages/components/alert.md +14 -9
- package/docs/pages/components/app-navigation.md +2 -0
- package/docs/pages/components/aura.md +102 -6
- package/docs/pages/components/avatar.md +9 -6
- package/docs/pages/components/badge.md +27 -9
- package/docs/pages/components/button.md +25 -18
- package/docs/pages/components/card.md +27 -7
- package/docs/pages/components/chat.md +1 -1
- package/docs/pages/components/data-list.md +72 -0
- package/docs/pages/components/dialog.md +12 -7
- package/docs/pages/components/drawer.md +3 -1
- package/docs/pages/components/fab.md +14 -10
- package/docs/pages/components/indicator.md +6 -6
- package/docs/pages/components/list.md +11 -2
- package/docs/pages/components/meter.md +56 -1
- package/docs/pages/components/navbar.md +1 -1
- package/docs/pages/components/pagination.md +1 -1
- package/docs/pages/components/progress.md +10 -0
- package/docs/pages/components/rating.md +4 -4
- package/docs/pages/components/skeleton.md +3 -1
- package/docs/pages/components/spinner.md +1 -1
- package/docs/pages/components/steps.md +379 -47
- package/docs/pages/components/table.md +6 -5
- package/docs/pages/enhancements/flyout.md +14 -10
- package/docs/pages/enhancements/overview.md +2 -2
- package/docs/pages/enhancements/scrollspy.md +1 -1
- package/docs/pages/enhancements/status-bar.md +10 -1
- package/docs/pages/enhancements/tabs.md +1 -1
- package/docs/pages/examples/overview.md +4 -1
- package/docs/pages/forms/choice-cards.md +4 -4
- package/docs/pages/forms/combobox.md +87 -0
- package/docs/pages/forms/floating-field.md +5 -3
- package/docs/pages/forms/joined-controls.md +6 -4
- package/docs/pages/forms/labels.md +8 -5
- package/docs/pages/forms/overview.md +26 -19
- package/docs/pages/forms/password.md +3 -2
- package/docs/pages/forms/range.md +15 -2
- package/docs/pages/forms/states-and-sizes.md +7 -5
- package/docs/pages/forms/validation.md +15 -9
- package/docs/pages/foundations/philosophy.md +11 -1
- package/docs/pages/foundations/tokens.md +16 -9
- package/docs/pages/foundations/typography.md +11 -3
- package/docs/pages/guides/bootstrap.md +3 -3
- package/docs/pages/guides/introduction.md +2 -2
- package/docs/pages/guides/modular-import.md +21 -10
- package/docs/pages/guides/progressive-enhancement.md +75 -54
- package/docs/pages/guides/tailwind.md +2 -2
- package/docs/pages/layout/app-shell.md +4 -4
- package/docs/pages/layout/choosing.md +10 -6
- package/docs/pages/layout/grid.md +11 -10
- package/docs/pages/layout/scroll-snap.md +2 -0
- package/docs/pages/layout/sidebar-layout.md +2 -0
- package/docs/pages/layout/stack.md +10 -6
- package/docs/pages/layout/topbar.md +2 -0
- package/docs/pages/patterns/structured-lists.md +7 -7
- package/docs/pages/utilities/accessibility.md +19 -1
- package/docs/pages/utilities/flex.md +20 -13
- package/docs/pages/utilities/links.md +1 -1
- package/docs/pages/utilities/list.md +1 -1
- package/docs/pages/utilities/overflow.md +14 -7
- package/docs/pages/utilities/overview.md +22 -0
- package/docs/pages/utilities/shape.md +1 -1
- package/docs/pages/utilities/sizing-wrapping.md +10 -8
- package/docs/pages/utilities/spacing.md +26 -19
- package/docs/pages/utilities/text.md +1 -1
- package/llms.txt +27 -4
- package/package.json +3 -2
- package/scripts/reserved-classes.json +8 -1
- package/src/css/components/data-list.css +27 -0
- package/src/css/components/drawer.css +30 -11
- package/src/css/components/fab.css +44 -8
- package/src/css/components/flyout.css +17 -12
- package/src/css/components/index.css +1 -0
- package/src/css/components/list.css +3 -1
- package/src/css/components/meter.css +28 -1
- package/src/css/components/modal.css +28 -11
- package/src/css/components/progress.css +5 -0
- package/src/css/components/status-bar.css +31 -15
- package/src/css/components/steps.css +289 -55
- package/src/css/components/surface.css +5 -3
- package/src/css/components/tooltip.css +5 -8
- package/src/css/core/base.css +7 -1
- package/src/css/core/intents.css +6 -0
- package/src/css/core/tokens.css +24 -9
- package/src/css/core/variants.css +17 -4
- package/src/css/effects/aura.css +101 -11
- package/src/css/forms/custom-select.css +1 -1
- package/src/css/forms/floating-field.css +8 -6
- package/src/css/forms/form.css +4 -4
- package/src/css/layout/container-query.css +37 -13
- package/src/css/layout/grid.css +7 -7
- package/src/css/layout/index.css +1 -1
- package/src/css/typography/prose.css +2 -3
- package/src/css/utilities/base.css +16 -0
- package/src/js/password.js +1 -1
- package/src/js/selectors.js +1 -0
- package/src/js/status.js +88 -7
- package/src/js/surface.js +2 -11
- package/src/js/transition.js +20 -0
package/dist/actual.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
*, *::before, *::after { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; } button, input, textarea, select { font: inherit; } [hidden]:not([hidden="until-found"]) { display: none !important; } :where(img, svg, video) { max-inline-size: 100%; block-size: auto; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto !important; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0s !important; } } :root { --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --line-height: 1.5; --line-height-tight: 1.25; --line-height-relaxed: 1.75; --font-weight: 400; --font-weight-medium: 500; --font-weight-strong: 650; --font-weight-bold: 700; --font-size-xs: 0.8125rem; --font-size-sm: 0.875rem; --font-size-md: 1rem; --font-size-lg: 1.125rem; --gap: 0.75rem; --density-space: var(--space-40); --density-compact-size: 1.5rem; --space-10: 0.25rem; --space-20: 0.5rem; --space-30: 0.75rem; --space-40: 1rem; --space-50: 1.5rem; --space-60: 2rem; --border-width: 1px; --radius-full: 9999px; --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04); --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15); --duration: 150ms; --duration-fast: 100ms; --duration-slow: 200ms; --duration-spin: 0.75s; --duration-shimmer: 1.2s; --ease: ease; --focus-outline-color: currentColor; --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color); --focus-outline-offset: 2px; --focus-ring-width: 3px; --indicator-offset: 14.65%; --backdrop-color: rgb(0 0 0); --backdrop-opacity: 0.45; --backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity)); --bar-height: 0.65rem; --control-size-sm: 2rem; --control-size-md: 2.375rem; --control-size-lg: 2.75rem; --control-size: var(--control-size-md); --control-pad-x: 1em; --control-font-size: var(--font-size-md); --choice-control-size: 1.125em; --disabled-opacity: 0.65; } :root, [data-theme] { --state-selected: var(--primary); --state-selected-fg: var(--primary-fg); --state-disabled: var(--text-subtle); --indicator-ring: var(--surface); } :root { --soft-bg-mix: 88%; --soft-border-mix: 65%; --soft-hover-alpha: 12%; --soft-fg-mix: 100%; --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E"); --viewport-block: 100vh; --viewport-inline: 100vw; --z-sticky: 10; --z-menu: 20; --z-tooltip: 50; --z-status: 60; } @media (forced-colors: active) { :root, [data-theme] { --state-disabled: GrayText; --disabled-opacity: 1; } } @supports (block-size: 100dvh) { :root { --viewport-block: 100dvh; } } @supports (inline-size: 100vi) { :root { --viewport-inline: 100vi; } } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent), 0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent); --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent); } :root { --backdrop-fill: color-mix( in oklch, var(--backdrop-color) calc(var(--backdrop-opacity) * 100%), transparent ); } } :root { color-scheme: light dark; } [data-theme="light"] { color-scheme: light; } [data-theme="dark"] { color-scheme: dark; } @supports not (color: light-dark(red, blue)) { :root, [data-theme="light"], [data-theme="dark"] { color-scheme: light; } } :root, [data-theme="light"] { --primary: hsl(268 32% 33%); --primary-fg: hsl(0 0% 98%); --secondary: hsl(16 38% 38%); --secondary-fg: hsl(0 0% 98%); --success: hsl(150 40% 31%); --success-fg: hsl(0 0% 98%); --warning: hsl(36 68% 34%); --warning-fg: hsl(0 0% 98%); --danger: hsl(5 58% 43%); --danger-fg: hsl(0 0% 98%); --neutral: hsl(258 7% 43%); --neutral-fg: hsl(0 0% 98%); --surface: hsl(0 0% 100%); --surface-raised: hsl(0 0% 100%); --surface-subtle: hsl(262 24% 96%); --surface-solid: hsl(261 16% 13%); --text: hsl(261 20% 13%); --text-muted: hsl(259 9% 42%); --text-subtle: hsl(258 7% 57%); --border: hsl(260 15% 88%); --focus: var(--neutral); --focus-ring: hsl(258 7% 43% / 0.3); --hover-overlay: hsl(260 20% 10% / 0.04); --radius: 0.5rem; --radius-sm: 0.25rem; --radius-lg: 0.75rem; --shadow-color: hsl(260 25% 10%); --hgroup-gap: 0.25em; } :root, [data-theme] { --heading: var(--text); --selection-bg: var(--primary); --selection-fg: var(--primary-fg); --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring); } :root, [data-theme] { accent-color: var(--primary); } [data-tone="dark"] { --tone-fg: white; } [data-tone="light"] { --tone-fg: var(--text); } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent); } } @supports (color: light-dark(red, blue)) { :root, [data-theme="light"] { --primary: light-dark(hsl(268 32% 33%), hsl(268 42% 76%)); --primary-fg: light-dark(hsl(0 0% 98%), hsl(268 30% 12%)); --secondary: light-dark(hsl(16 38% 38%), hsl(18 45% 72%)); --secondary-fg: light-dark(hsl(0 0% 98%), hsl(18 32% 11%)); --success: light-dark(hsl(150 40% 31%), hsl(150 38% 62%)); --success-fg: light-dark(hsl(0 0% 98%), hsl(150 35% 8%)); --warning: light-dark(hsl(36 68% 34%), hsl(40 62% 64%)); --warning-fg: light-dark(hsl(0 0% 98%), hsl(38 35% 10%)); --danger: light-dark(hsl(5 58% 43%), hsl(4 60% 70%)); --danger-fg: light-dark(hsl(0 0% 98%), hsl(4 35% 10%)); --neutral: light-dark(hsl(258 7% 43%), hsl(258 10% 72%)); --neutral-fg: light-dark(hsl(0 0% 98%), hsl(260 18% 10%)); --surface: light-dark(hsl(0 0% 100%), hsl(263 17% 9%)); --surface-raised: light-dark(hsl(0 0% 100%), hsl(263 15% 12%)); --surface-subtle: light-dark(hsl(262 24% 96%), hsl(263 13% 16%)); --surface-solid: light-dark(hsl(261 16% 13%), hsl(0 0% 96%)); --text: light-dark(hsl(261 20% 13%), hsl(260 12% 95%)); --text-muted: light-dark(hsl(259 9% 42%), hsl(259 9% 67%)); --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%)); --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%)); --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28)); --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06)); --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%)); } } @supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) { :root, [data-theme] { --focus-ring: light-dark( color-mix(in oklch, var(--focus) 30%, transparent), color-mix(in oklch, var(--focus) 28%, transparent) ); } } @media (prefers-contrast: more) { :root, [data-theme][data-theme] { --border-width: 2px; --focus-outline-offset: 3px; --focus-ring-width: 4px; --text-muted: var(--text); --text-subtle: var(--text); } } body { background: var(--surface); color: var(--text); font-family: var(--font-sans); line-height: var(--line-height); } a { color: inherit; text-underline-offset: 0.18em; } a:hover { text-decoration-thickness: 0.12em; } :where(h1, h2, h3, h4, h5, h6) { color: var(--heading, inherit); line-height: var(--line-height-tight); text-wrap: balance; } hgroup { display: grid; gap: var(--hgroup-gap); } hgroup > * { margin-block: 0; } hr { border: 0; border-block-start: var(--border-width) solid var(--border); margin-block: var(--space-60); } code, kbd, samp, pre { font-family: var(--font-mono); } small { font-size: 0.875em; } ::selection { background: var(--selection-bg, var(--primary)); color: var(--selection-fg, var(--primary-fg)); } .primary { --intent: var(--primary); --intent-fg: var(--primary-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); } .solid { --ui-bg: var(--intent, var(--neutral)); --ui-fg: var(--intent-fg, var(--neutral-fg)); --ui-border: transparent; } .soft { --ui-bg: var(--surface-subtle); --ui-border: var(--border); --ui-fg: var(--intent, var(--text)); } .outline { --ui-bg: transparent; --ui-fg: var(--intent, currentColor); --ui-border: var(--intent, currentColor); } .inverted { --ui-bg: var(--surface-solid); --ui-fg: var(--surface); --ui-border: transparent; --ui-hover-bg: var(--surface-solid); --heading: var(--surface); --busy-overlay-bg: var(--surface-solid); background: var(--ui-bg); color: var(--ui-fg); border-color: var(--ui-border); } .sm { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); --density-compact-size: 1.25rem; } .lg { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); --density-compact-size: 1.875rem; } @supports (color: color-mix(in oklch, red, white)) { .soft { --ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral))); --ui-border: color-mix( in oklab, var(--surface) var(--soft-border-mix), var(--intent, var(--neutral)) ); --ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix)); } .inverted { --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black); } } :focus { outline: var(--focus-outline); outline-offset: var(--focus-outline-offset); } @supports selector(:focus-visible) { :focus:not(:focus-visible) { outline: none; } } @media print { :root, body { color-scheme: light; background: #fff; color: #000; } h1, h2, h3, h4, h5, h6 { break-after: avoid; } }
|
|
1
|
+
*, *::before, *::after { box-sizing: border-box; } html { -webkit-text-size-adjust: 100%; } body { margin: 0; } button, input, textarea, select { font: inherit; } [hidden]:not([hidden="until-found"]) { display: none !important; } :where(img, svg, video) { max-inline-size: 100%; block-size: auto; } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto !important; } *, *::before, *::after { animation-duration: 0.01ms !important; animation-delay: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; transition-delay: 0s !important; } } :root { --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; --line-height: 1.5; --line-height-tight: 1.25; --line-height-relaxed: 1.75; --font-weight: 400; --font-weight-medium: 500; --font-weight-strong: 650; --font-weight-bold: 700; --font-size-xs: 0.8125rem; --font-size-sm: 0.875rem; --font-size-md: 1rem; --font-size-lg: 1.125rem; --gap: 0.75rem; --density-space: var(--space-40); --density-compact-size: 1.5rem; --space-10: 0.25rem; --space-20: 0.5rem; --space-30: 0.75rem; --space-40: 1rem; --space-50: 1.5rem; --space-60: 2rem; --border-width: 1px; --radius-full: 9999px; --shadow: 0 1px 3px rgb(0 0 0 / 0.05), 0 4px 12px rgb(0 0 0 / 0.04); --shadow-popout: 0 0.25rem 0.75rem rgb(0 0 0 / 0.15); --duration: 150ms; --duration-fast: 100ms; --duration-slow: 200ms; --duration-spin: 0.75s; --duration-shimmer: 1.2s; --ease-enter: cubic-bezier(0.2, 0, 0, 1); --ease-exit: cubic-bezier(0.4, 0, 1, 1); --focus-outline-color: currentColor; --focus-outline: calc(var(--border-width) * 2) solid var(--focus-outline-color); --focus-outline-offset: 2px; --focus-ring-width: 3px; --indicator-offset: 14.65%; --backdrop-color: rgb(0 0 0); --backdrop-opacity: 0.45; --backdrop-fill: rgb(0 0 0 / var(--backdrop-opacity)); --bar-height: 0.65rem; --control-size-sm: 2rem; --control-size-md: 2.375rem; --control-size-lg: 2.75rem; --control-size: var(--control-size-md); --control-pad-x: 1em; --control-font-size: var(--font-size-md); --choice-control-size: 1.125em; --disabled-opacity: 0.65; } :root, [data-theme] { --state-selected: var(--primary); --state-selected-fg: var(--primary-fg); --state-disabled: var(--text-subtle); --indicator-ring: var(--surface); } :root { --soft-bg-mix: 88%; --soft-border-mix: 65%; --soft-hover-alpha: 12%; --soft-fg-mix: 100%; --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a8594' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E"); --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3C/svg%3E"); --icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E"); --viewport-block: 100vh; --viewport-inline: 100vw; --z-sticky: 10; --z-menu: 20; --z-tooltip: 50; --z-status: 60; } @media (forced-colors: active) { :root, [data-theme] { --state-disabled: GrayText; --disabled-opacity: 1; } } @supports (block-size: 100dvh) { :root { --viewport-block: 100dvh; } } @supports (inline-size: 100vi) { :root { --viewport-inline: 100vi; } } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --shadow: 0 1px 3px color-mix(in oklch, var(--shadow-color) 5%, transparent), 0 4px 12px color-mix(in oklch, var(--shadow-color) 4%, transparent); --shadow-popout: 0 0.25rem 0.75rem color-mix(in oklch, var(--shadow-color) 15%, transparent); } :root { --backdrop-fill: color-mix( in oklch, var(--backdrop-color) calc(var(--backdrop-opacity) * 100%), transparent ); } } :root { color-scheme: light dark; } [data-theme="light"] { color-scheme: light; } [data-theme="dark"] { color-scheme: dark; } @supports not (color: light-dark(red, blue)) { :root, [data-theme="light"], [data-theme="dark"] { color-scheme: light; } } :root, [data-theme="light"] { --primary: hsl(268 32% 33%); --primary-fg: hsl(0 0% 98%); --secondary: hsl(16 38% 38%); --secondary-fg: hsl(0 0% 98%); --success: hsl(150 40% 31%); --success-fg: hsl(0 0% 98%); --warning: hsl(36 68% 34%); --warning-fg: hsl(0 0% 98%); --danger: hsl(5 58% 43%); --danger-fg: hsl(0 0% 98%); --neutral: hsl(258 7% 43%); --neutral-fg: hsl(0 0% 98%); --surface: hsl(0 0% 100%); --surface-raised: hsl(0 0% 100%); --surface-subtle: hsl(262 24% 96%); --surface-solid: hsl(261 16% 13%); --text: hsl(261 20% 13%); --text-muted: hsl(259 9% 42%); --text-subtle: hsl(258 7% 57%); --border: hsl(260 15% 88%); --focus: var(--neutral); --focus-ring: hsl(258 7% 43% / 0.3); --hover-overlay: hsl(260 20% 10% / 0.04); --radius: 0.5rem; --radius-sm: 0.25rem; --radius-lg: 0.75rem; --shadow-color: hsl(260 25% 10%); --hgroup-gap: 0.25em; } :root, [data-theme] { --heading: var(--text); --selection-bg: var(--primary); --selection-fg: var(--primary-fg); --focus-ring-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring); } :root, [data-theme] { accent-color: var(--primary); } [data-tone="dark"] { --tone-fg: white; } [data-tone="light"] { --tone-fg: var(--text); } @supports (color: color-mix(in oklch, red, white)) { :root, [data-theme] { --focus-ring: color-mix(in oklch, var(--focus) 30%, transparent); } } @supports (color: light-dark(red, blue)) { :root, [data-theme="light"] { --primary: light-dark(hsl(268 32% 33%), hsl(268 42% 76%)); --primary-fg: light-dark(hsl(0 0% 98%), hsl(268 30% 12%)); --secondary: light-dark(hsl(16 38% 38%), hsl(18 45% 72%)); --secondary-fg: light-dark(hsl(0 0% 98%), hsl(18 32% 11%)); --success: light-dark(hsl(150 40% 31%), hsl(150 38% 62%)); --success-fg: light-dark(hsl(0 0% 98%), hsl(150 35% 8%)); --warning: light-dark(hsl(36 68% 34%), hsl(40 62% 64%)); --warning-fg: light-dark(hsl(0 0% 98%), hsl(38 35% 10%)); --danger: light-dark(hsl(5 58% 43%), hsl(4 60% 70%)); --danger-fg: light-dark(hsl(0 0% 98%), hsl(4 35% 10%)); --neutral: light-dark(hsl(258 7% 43%), hsl(258 10% 72%)); --neutral-fg: light-dark(hsl(0 0% 98%), hsl(260 18% 10%)); --surface: light-dark(hsl(0 0% 100%), hsl(263 17% 9%)); --surface-raised: light-dark(hsl(0 0% 100%), hsl(263 15% 12%)); --surface-subtle: light-dark(hsl(262 24% 96%), hsl(263 13% 16%)); --surface-solid: light-dark(hsl(261 16% 13%), hsl(0 0% 96%)); --text: light-dark(hsl(261 20% 13%), hsl(260 12% 95%)); --text-muted: light-dark(hsl(259 9% 42%), hsl(259 9% 67%)); --text-subtle: light-dark(hsl(258 7% 57%), hsl(258 7% 54%)); --border: light-dark(hsl(260 15% 88%), hsl(262 12% 24%)); --focus-ring: light-dark(hsl(258 7% 43% / 0.3), hsl(258 10% 72% / 0.28)); --hover-overlay: light-dark(hsl(260 20% 10% / 0.04), hsl(0 0% 100% / 0.06)); --shadow-color: light-dark(hsl(260 25% 10%), hsl(262 40% 2%)); } } @supports (color: color-mix(in oklch, red, white)) and (color: light-dark(red, blue)) { :root, [data-theme] { --focus-ring: light-dark( color-mix(in oklch, var(--focus) 30%, transparent), color-mix(in oklch, var(--focus) 28%, transparent) ); } } @media (prefers-contrast: more) { :root, [data-theme][data-theme] { --border-width: 2px; --focus-outline-offset: 3px; --focus-ring-width: 4px; --text-muted: var(--text); --text-subtle: var(--text); } } body { background: var(--surface); color: var(--text); font-family: var(--font-sans); line-height: var(--line-height); } a { color: var(--link, inherit); text-underline-offset: 0.18em; } a:hover { text-decoration-thickness: 0.12em; } :where(h1, h2, h3, h4, h5, h6) { color: var(--heading, inherit); line-height: var(--line-height-tight); text-wrap: balance; } hgroup { display: grid; gap: var(--hgroup-gap); } hgroup > * { margin-block: 0; } hr { border: 0; border-block-start: var(--border-width) solid var(--border); margin-block: var(--space-60); } code, kbd, samp, pre { font-family: var(--font-mono); } small { font-size: 0.875em; } ::selection { background: var(--selection-bg, var(--primary)); color: var(--selection-fg, var(--primary-fg)); } .primary { --intent: var(--primary); --intent-fg: var(--primary-fg); } .secondary { --intent: var(--secondary); --intent-fg: var(--secondary-fg); } .success { --intent: var(--success); --intent-fg: var(--success-fg); } .warning { --intent: var(--warning); --intent-fg: var(--warning-fg); } .danger { --intent: var(--danger); --intent-fg: var(--danger-fg); } .neutral { --intent: var(--neutral); --intent-fg: var(--neutral-fg); } .intent-color { color: var(--intent); } .solid { --ui-bg: var(--intent, var(--neutral)); --ui-fg: var(--intent-fg, var(--neutral-fg)); --ui-border: transparent; } .soft { --ui-bg: var(--surface-subtle); --ui-border: var(--border); --ui-fg: var(--intent, var(--text)); } .outline { --ui-bg: transparent; --ui-fg: var(--intent, currentColor); --ui-border: var(--intent, currentColor); } .surface { --ui-bg: var(--surface); --ui-fg: var(--intent, var(--text)); --ui-border: var(--border); } .inverted { --ui-bg: var(--surface-solid); --ui-fg: var(--surface); --ui-border: transparent; --ui-hover-bg: var(--surface-solid); --heading: var(--surface); --busy-overlay-bg: var(--surface-solid); background: var(--ui-bg); color: var(--ui-fg); border-color: var(--ui-border); } .sm { --gap: var(--space-20); --density-space: var(--space-20); --control-size: var(--control-size-sm); --density-compact-size: 1.25rem; } .lg { --gap: var(--space-50); --density-space: var(--space-50); --control-size: var(--control-size-lg); --density-compact-size: 1.875rem; } @supports (color: color-mix(in oklch, red, white)) { .soft { --ui-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--intent, var(--neutral))); --ui-border: color-mix( in oklab, var(--surface) var(--soft-border-mix), var(--intent, var(--neutral)) ); --ui-fg: color-mix(in oklab, var(--text), var(--intent, var(--text)) var(--soft-fg-mix)); } .inverted { --ui-hover-bg: color-mix(in oklch, var(--surface-solid) 88%, black); } } :focus { outline: var(--focus-outline); outline-offset: var(--focus-outline-offset); } @supports selector(:focus-visible) { :focus:not(:focus-visible) { outline: none; } } @media print { :root, body { color-scheme: light; background: #fff; color: #000; } h1, h2, h3, h4, h5, h6 { break-after: avoid; } }
|
|
@@ -83,20 +83,20 @@ The tiers above are Actual's own contract. The table below tracks the Web
|
|
|
83
83
|
Platform Baseline of the individual CSS features the framework uses; it is a
|
|
84
84
|
different notion from an Actual tier.
|
|
85
85
|
|
|
86
|
-
| Feature area
|
|
87
|
-
|
|
88
|
-
| Layout (flex, grid, position)
|
|
89
|
-
| CSS custom properties
|
|
90
|
-
| `:has()`
|
|
91
|
-
| `<dialog>` element
|
|
92
|
-
| `color-mix()`
|
|
93
|
-
| `light-dark()`
|
|
94
|
-
| `:user-invalid`
|
|
95
|
-
| `transition-behavior: allow-discrete` | 2024+
|
|
96
|
-
| `appearance: base-select`
|
|
97
|
-
| `@starting-style`
|
|
98
|
-
| View Transition API
|
|
99
|
-
| CSS Anchor Positioning
|
|
86
|
+
| Feature area | Baseline | Progressive enhancement |
|
|
87
|
+
| ------------------------------------- | ------------- | ------------------------------ |
|
|
88
|
+
| Layout (flex, grid, position) | Baseline 2023 | — |
|
|
89
|
+
| CSS custom properties | Baseline 2023 | — |
|
|
90
|
+
| `:has()` | Baseline 2023 | — |
|
|
91
|
+
| `<dialog>` element | Baseline 2023 | — |
|
|
92
|
+
| `color-mix()` | Baseline 2023 | Fallback flat color |
|
|
93
|
+
| `light-dark()` | 2024+ | Manual theme override |
|
|
94
|
+
| `:user-invalid` | 2024+ | `[aria-invalid]` attribute |
|
|
95
|
+
| `transition-behavior: allow-discrete` | 2024+ | Native dialog open/close |
|
|
96
|
+
| `appearance: base-select` | 2024+ | Native `<select>` |
|
|
97
|
+
| `@starting-style` | 2024+ | Instant appearance |
|
|
98
|
+
| View Transition API | 2024+ | Dialog open/close transition |
|
|
99
|
+
| CSS Anchor Positioning | Future | JS positioning (`floating.js`) |
|
|
100
100
|
|
|
101
101
|
Core layout and visual tokens work on all Baseline 2023 browsers. Features marked
|
|
102
102
|
2024+ are gated with `@supports` or degrade gracefully through fallback values.
|
|
@@ -105,22 +105,39 @@ defeating the split.
|
|
|
105
105
|
|
|
106
106
|
## Design decisions recorded
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
**D2 — `selectors.js` drops its 5 discovery entries and keeps 11 write-side
|
|
109
|
+
state entries.** The file's job changes from *CSS↔init bridge* to *state
|
|
110
|
+
vocabulary the runtime writes*. It stays the single file to alias for another
|
|
111
|
+
CSS framework.
|
|
112
|
+
|
|
113
|
+
**D4 — `status.js` uses `[data-status][role="status"]`, with no class and no
|
|
114
|
+
token.** The markup already carries `role="status"` everywhere, so semantics
|
|
115
|
+
alone scope the document-wide lookup.
|
|
116
|
+
|
|
117
|
+
**D5 — `<dialog>` gets no token.** It is its own opt-in: semantic, native,
|
|
118
|
+
CSS-agnostic.
|
|
119
|
+
|
|
120
|
+
**D6 — `validation.js` accepts `[data-field-error]` as an alias for
|
|
121
|
+
`.field-error`, and keeps `.field` / `.danger` as an optional, degrading
|
|
122
|
+
presentation adapter.** Validation is the one module with intentional, optional
|
|
123
|
+
CSS coupling. The adapter degrades to a no-op under foreign CSS, while
|
|
124
|
+
`aria-invalid`, focus management, custom rules and server errors all keep
|
|
125
|
+
working.
|
|
126
|
+
|
|
127
|
+
**D7 — Context-menu reuses `surface`, not `flyout`.** The panel gets no token;
|
|
128
|
+
`menuFor()` validates semantically (`menu, [role="menu"]`). A context menu may
|
|
129
|
+
legitimately want the flyout *look* without the flyout *behavior*.
|
|
130
|
+
|
|
131
|
+
**D9 — Orphaned mounted surfaces are closed from the origin's disconnect
|
|
132
|
+
handler.** A `fix:`, not part of the contract itself. Landed independently.
|
|
116
133
|
|
|
117
134
|
## Token vocabulary
|
|
118
135
|
|
|
119
|
-
| Token
|
|
120
|
-
|
|
121
|
-
| `tabs`
|
|
122
|
-
| `flyout`
|
|
123
|
-
| `scrollspy`
|
|
136
|
+
| Token | Behavior | Existing class |
|
|
137
|
+
| ------------ | ------------------------------------------------ | ------------------- |
|
|
138
|
+
| `tabs` | Tab panel switching, arrow-key navigation | `.tabs` |
|
|
139
|
+
| `flyout` | Trigger → panel positioning, open/close | `.flyout` |
|
|
140
|
+
| `scrollspy` | Scroll-driven nav highlighting | `.scrollspy` |
|
|
124
141
|
| `validation` | Form validation, focus management, server errors | `.needs-validation` |
|
|
125
142
|
|
|
126
143
|
Four tokens. Everything else stays self-describing.
|
|
@@ -53,13 +53,17 @@ an essential shape, state, or affordance.
|
|
|
53
53
|
|
|
54
54
|
## Color system keywords by role
|
|
55
55
|
|
|
56
|
-
| Context
|
|
57
|
-
|
|
58
|
-
| Disclosure
|
|
59
|
-
| Button, switch, checkbox |
|
|
60
|
-
| Selected / current state | `Highlight
|
|
61
|
-
| Disabled state
|
|
62
|
-
| Hover indicator
|
|
56
|
+
| Context | Color | Why |
|
|
57
|
+
| ------------------------ | --------------------- | ---------------------------------- |
|
|
58
|
+
| Disclosure marker | `CanvasText` | Summary is on Canvas, not a button |
|
|
59
|
+
| Button, switch, checkbox | the `Button*` pair | Interactive controls |
|
|
60
|
+
| Selected / current state | the `Highlight*` pair | Matches OS selection |
|
|
61
|
+
| Disabled state | `GrayText` | Standard disabled system color |
|
|
62
|
+
| Hover indicator | outline or underline | Avoids blocked backgrounds |
|
|
63
|
+
|
|
64
|
+
Controls use `ButtonText` / `ButtonFace`, and `Highlight` / `HighlightText` for
|
|
65
|
+
their selected state. A hover indicator is a `ButtonText` outline or a
|
|
66
|
+
`CanvasText` underline.
|
|
63
67
|
|
|
64
68
|
Prefer `CanvasText` for non-interactive mark-up content. `CanvasText` and
|
|
65
69
|
`ButtonText` are **not guaranteed identical** — custom HCM palettes can set
|
|
@@ -254,7 +254,7 @@ asks.
|
|
|
254
254
|
It is nevertheless the right **baseline**. Bounded at N, using `auto-fill`, it
|
|
255
255
|
is responsive, overflow-safe, and equal-width at every state, and it needs
|
|
256
256
|
nothing above Actual's Minimal floor. Adopting it as the unenhanced behavior of
|
|
257
|
-
`.grid-N` is what makes the actual-
|
|
257
|
+
`.grid-N` is what makes the actual-container container a precision enhancement rather than
|
|
258
258
|
a prerequisite for responsiveness. This is the single most important difference
|
|
259
259
|
from 0.3.1, where a preset used without a wrapper stayed frozen at N columns
|
|
260
260
|
and was a genuine trap.
|
|
@@ -391,7 +391,7 @@ The extra ancestor is therefore not accidental ceremony.
|
|
|
391
391
|
|
|
392
392
|
It represents a real boundary imposed by the CSS containment model.
|
|
393
393
|
|
|
394
|
-
Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid. Balanced subdivision keys off a container **named** `actual-
|
|
394
|
+
Actual should not hide that boundary if hiding it requires changing the sizing semantics of the grid. Balanced subdivision keys off a container **named** `actual-container` rather than any anonymous query container, so an incidental `container-type: inline-size` region never changes the grid by accident; `.container-query` is the convenience helper that establishes that named context. The name is generic rather than grid-specific: it means "here is the width you were allocated", and other size-aware components (`.steps` and its label collapse) read the same context. The grid does not own it — it is one consumer.
|
|
395
395
|
|
|
396
396
|
## Out of scope: explicit per-region placement
|
|
397
397
|
|
|
@@ -520,7 +520,7 @@ viewport
|
|
|
520
520
|
|
|
|
521
521
|
named query container
|
|
522
522
|
|
|
|
523
|
-
actual-
|
|
523
|
+
actual-container
|
|
524
524
|
|
|
|
525
525
|
2 / 3 / 4 / 6 stages
|
|
526
526
|
```
|
|
@@ -620,7 +620,7 @@ inside a query container.
|
|
|
620
620
|
.grid space-driven, auto-fit, --grid-min
|
|
621
621
|
.grid-N count-driven baseline, auto-fill bounded at N,
|
|
622
622
|
may pass through non-divisor column counts
|
|
623
|
-
actual-
|
|
623
|
+
actual-container .grid-N balanced subdivision, N -> divisors of N -> 1
|
|
624
624
|
```
|
|
625
625
|
|
|
626
626
|
The whole enhancement lives inside `@supports (container-type: inline-size)`.
|
|
@@ -40,15 +40,21 @@ Actual CSS is intended for new projects. Existing projects with class collisions
|
|
|
40
40
|
|
|
41
41
|
## Utilities
|
|
42
42
|
|
|
43
|
-
Utilities ship in two
|
|
43
|
+
Utilities ship in two modules that follow the same class grammar. Both are
|
|
44
|
+
imported by `utilities/index.css`, so the split is about naming and curation,
|
|
45
|
+
never about opting in.
|
|
44
46
|
|
|
45
|
-
- **Base utilities
|
|
46
|
-
(`.overflow-auto`, `.gap-none`, `.text-nowrap`, `.px`, `.py`).
|
|
47
|
-
- **
|
|
47
|
+
- **Base** (`utilities/base.css`) is a curated set of common ergonomic
|
|
48
|
+
shortcuts (`.overflow-auto`, `.gap-none`, `.text-nowrap`, `.px`, `.py`).
|
|
49
|
+
- **Extra** (`utilities/extra.css`) uses explicit property/value names
|
|
48
50
|
(`.overflow-hidden`, `.gap-sm`, `.white-space-normal`).
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
Call them base and extra, after the files. "Optional" is the wrong word for
|
|
53
|
+
either: it reads as a layer an author has to opt into, which is true of every
|
|
54
|
+
module outside `core/` and therefore says nothing about these two.
|
|
55
|
+
|
|
56
|
+
Base may use compact names for a small set of frequent operations (`.px`,
|
|
57
|
+
`.py`, `.mbs`, `.mbe`). Extra uses explicit property/value names and must not
|
|
58
|
+
duplicate an operation already provided by base. The two sets may cover the
|
|
59
|
+
same CSS property, but must not expose competing names for the same
|
|
60
|
+
declaration.
|
|
@@ -287,6 +287,15 @@ Avoid building a large custom range abstraction around browser-specific pseudo-e
|
|
|
287
287
|
|
|
288
288
|
Prefer native controls and progressive styling.
|
|
289
289
|
|
|
290
|
+
The concrete signal to watch is `appearance: base-slider` with `::slider-track`, `::slider-fill`, and `::slider-thumb`. Checked on Chrome 152: `CSS.supports('appearance', 'base-slider')` and `CSS.supports('selector(::slider-fill)')` are both false, so there is nothing to enhance against yet.
|
|
291
|
+
|
|
292
|
+
Until then, `.range` intentionally omits filled tracks and tick marks. Both are reachable today only through techniques Actual should not take on:
|
|
293
|
+
|
|
294
|
+
- The Chromium filled-track trick needs `overflow: hidden` on the runnable track plus a large `box-shadow` on the thumb. It collides twice with `range.css`: the thumb is wider than the track and would be clipped, and the focus ring already owns the thumb's `box-shadow`.
|
|
295
|
+
- CSS-only tick marks need a step count duplicated out of `min`/`max`/`step` into a custom property, so markup carries the same contract twice and the two can diverge. `datalist` cannot carry it either: its native ticks are part of the appearance that `appearance: none` removes, and no engine renders `<option label>` for a range.
|
|
296
|
+
|
|
297
|
+
When the base-slider parts become interoperable, this becomes a `@supports` enhancement in the shape of `custom-select.css` — less engine-specific CSS, not more.
|
|
298
|
+
|
|
290
299
|
### Navigation
|
|
291
300
|
|
|
292
301
|
Native navigation proposals may eventually provide richer semantics and interaction behavior for complex navigation structures.
|
|
@@ -330,3 +330,7 @@ Prior Actual CSS libraries:
|
|
|
330
330
|
- https://getbootstrap.com/docs/5.3/components/scrollspy/
|
|
331
331
|
- https://una.im/scroll-target-group/
|
|
332
332
|
- https://www.sarasoueidan.com/blog/css-scrollspy/
|
|
333
|
+
|
|
334
|
+
### @container
|
|
335
|
+
|
|
336
|
+
- https://ishadeed.com/article/css-container-query-guide/
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# Spacing Units: When to Use `em`, `rem`, `ch`, `lh`, or `--space-*`
|
|
2
|
+
|
|
3
|
+
Actual does not require every relative length to come from a design token.
|
|
4
|
+
|
|
5
|
+
CSS relative units already carry useful semantics. A literal `0.5em`, `1lh`, `20rem`, or `65ch` can be more meaningful than replacing it with an unrelated spacing token.
|
|
6
|
+
|
|
7
|
+
The goal is not to eliminate literal values. The goal is to choose the unit that best expresses the relationship.
|
|
8
|
+
|
|
9
|
+
## Core rule
|
|
10
|
+
|
|
11
|
+
Use `--space-*` when the value belongs to the **shared interface rhythm**.
|
|
12
|
+
|
|
13
|
+
Use a CSS relative unit when the value belongs to the **intrinsic geometry or typography of the component**.
|
|
14
|
+
|
|
15
|
+
A useful question is:
|
|
16
|
+
|
|
17
|
+
> If this value changes, is that a design-system decision or a local relationship inside the component?
|
|
18
|
+
|
|
19
|
+
If it is a design-system decision, prefer a token.
|
|
20
|
+
|
|
21
|
+
If it is a local relationship, prefer the appropriate CSS unit.
|
|
22
|
+
|
|
23
|
+
## `--space-*`: interface rhythm
|
|
24
|
+
|
|
25
|
+
Use spacing tokens for layout distances that should remain consistent across components and respond to Actual's density system.
|
|
26
|
+
|
|
27
|
+
Typical cases:
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
.card {
|
|
31
|
+
padding: var(--space-40);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.cluster {
|
|
35
|
+
gap: var(--space-20);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.data-list {
|
|
39
|
+
gap: var(--space-20) var(--space-40);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Good candidates include:
|
|
44
|
+
|
|
45
|
+
* component padding
|
|
46
|
+
* gaps between peer controls
|
|
47
|
+
* spacing between sections
|
|
48
|
+
* list and grid gaps
|
|
49
|
+
* toolbar spacing
|
|
50
|
+
* application layout rhythm
|
|
51
|
+
|
|
52
|
+
Using a spacing token also communicates that the value is expected to participate in density changes.
|
|
53
|
+
|
|
54
|
+
Do not use `--space-*` merely to avoid writing a literal relative unit.
|
|
55
|
+
|
|
56
|
+
## `em`: relative to the local text
|
|
57
|
+
|
|
58
|
+
Use `em` when a dimension should scale with the font size of the element or component.
|
|
59
|
+
|
|
60
|
+
Typical cases:
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
.prose dd {
|
|
64
|
+
margin-inline-start: 1.5em;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.inline-icon {
|
|
68
|
+
margin-inline-end: 0.4em;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Good candidates include:
|
|
73
|
+
|
|
74
|
+
* typographic indentation
|
|
75
|
+
* small offsets around inline icons
|
|
76
|
+
* decorative elements sized relative to text
|
|
77
|
+
* local relationships that should scale when the component font size changes
|
|
78
|
+
|
|
79
|
+
`em` is especially useful when the relationship is best understood as:
|
|
80
|
+
|
|
81
|
+
> some fraction or multiple of this text size
|
|
82
|
+
|
|
83
|
+
Do not use `em` for general application spacing when local font-size changes should not affect the layout.
|
|
84
|
+
|
|
85
|
+
## `rem`: relative to the root scale
|
|
86
|
+
|
|
87
|
+
Use `rem` when a dimension should follow the user's/root font size but remain independent of a component's local typography.
|
|
88
|
+
|
|
89
|
+
Typical cases:
|
|
90
|
+
|
|
91
|
+
```css
|
|
92
|
+
.sidebar {
|
|
93
|
+
inline-size: 18rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dialog {
|
|
97
|
+
max-inline-size: 36rem;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Good candidates include:
|
|
102
|
+
|
|
103
|
+
* component size constraints
|
|
104
|
+
* layout thresholds
|
|
105
|
+
* widths and heights tied to the overall UI scale
|
|
106
|
+
* values that should remain stable when nested inside differently sized text
|
|
107
|
+
|
|
108
|
+
A literal `18rem` does not need a custom property unless the value itself is part of the public API, reused significantly, or expected to be configured.
|
|
109
|
+
|
|
110
|
+
## `ch`: text-measure relationships
|
|
111
|
+
|
|
112
|
+
Use `ch` when a dimension is naturally related to the approximate width of characters.
|
|
113
|
+
|
|
114
|
+
Typical cases:
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
.prose {
|
|
118
|
+
max-inline-size: 65ch;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.search-field {
|
|
122
|
+
inline-size: 24ch;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Good candidates include:
|
|
127
|
+
|
|
128
|
+
* readable text measures
|
|
129
|
+
* text-oriented input widths
|
|
130
|
+
* content regions whose useful size is determined by expected text length
|
|
131
|
+
|
|
132
|
+
Do not use `ch` as a generic substitute for `rem`; its value depends on font metrics.
|
|
133
|
+
|
|
134
|
+
## `lh`: line-height relationships
|
|
135
|
+
|
|
136
|
+
Use `lh` when a dimension should be directly related to the computed line height.
|
|
137
|
+
|
|
138
|
+
Typical cases:
|
|
139
|
+
|
|
140
|
+
```css
|
|
141
|
+
.floating-label {
|
|
142
|
+
inset-block-start: calc(0.5lh + var(--border-width));
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Good candidates include:
|
|
147
|
+
|
|
148
|
+
* vertical positioning relative to a line of text
|
|
149
|
+
* text-area or field geometry
|
|
150
|
+
* offsets that conceptually represent one or part of one line
|
|
151
|
+
|
|
152
|
+
`lh` is preferable to reconstructing line height indirectly from font size when line height is the actual relationship being expressed.
|
|
153
|
+
|
|
154
|
+
## Choosing between them
|
|
155
|
+
|
|
156
|
+
| Intent | Prefer |
|
|
157
|
+
| --------------------------------------- | ---------------- |
|
|
158
|
+
| Shared application spacing | `var(--space-*)` |
|
|
159
|
+
| Density-aware spacing | `var(--space-*)` |
|
|
160
|
+
| Local relationship to component text | `em` |
|
|
161
|
+
| Stable size relative to root typography | `rem` |
|
|
162
|
+
| Width related to text length | `ch` |
|
|
163
|
+
| Geometry related to line height | `lh` |
|
|
164
|
+
|
|
165
|
+
## Examples
|
|
166
|
+
|
|
167
|
+
### System rhythm
|
|
168
|
+
|
|
169
|
+
```css
|
|
170
|
+
.alert {
|
|
171
|
+
padding: var(--space-30) var(--space-40);
|
|
172
|
+
gap: var(--space-30);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The spacing is part of the visual density and should stay aligned with other components.
|
|
177
|
+
|
|
178
|
+
### Intrinsic typography
|
|
179
|
+
|
|
180
|
+
```css
|
|
181
|
+
.prose dd {
|
|
182
|
+
margin-inline-start: 1.5em;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The indentation belongs to the description text itself. A spacing token would add no useful meaning.
|
|
187
|
+
|
|
188
|
+
### Stable component width
|
|
189
|
+
|
|
190
|
+
```css
|
|
191
|
+
.sidebar {
|
|
192
|
+
flex-basis: 18rem;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The width belongs to the component geometry, not to the spacing scale.
|
|
197
|
+
|
|
198
|
+
### Readable prose
|
|
199
|
+
|
|
200
|
+
```css
|
|
201
|
+
.prose {
|
|
202
|
+
max-inline-size: 65ch;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The width exists to control line length, so `ch` expresses the intention directly.
|
|
207
|
+
|
|
208
|
+
### Line-relative positioning
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
.field-label {
|
|
212
|
+
inset-block-start: 0.5lh;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The position is defined relative to the text line, not to global spacing.
|
|
217
|
+
|
|
218
|
+
## Avoid token laundering
|
|
219
|
+
|
|
220
|
+
Do not replace meaningful CSS units with spacing tokens simply to make a stylesheet appear more tokenized.
|
|
221
|
+
|
|
222
|
+
Avoid patterns such as:
|
|
223
|
+
|
|
224
|
+
```css
|
|
225
|
+
.icon {
|
|
226
|
+
translate: 0 var(--space-10);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.prose {
|
|
230
|
+
max-inline-size: var(--space-900);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.label {
|
|
234
|
+
margin-inline-end: var(--space-20);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
when the real relationships would be better expressed as:
|
|
239
|
+
|
|
240
|
+
```css
|
|
241
|
+
.icon {
|
|
242
|
+
translate: 0 -0.1em;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.prose {
|
|
246
|
+
max-inline-size: 65ch;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.label {
|
|
250
|
+
margin-inline-end: 0.5em;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Tokens should add meaning, not hide it.
|
|
255
|
+
|
|
256
|
+
## Literal relative values are not a code smell
|
|
257
|
+
|
|
258
|
+
A well-chosen literal relative value is valid framework CSS.
|
|
259
|
+
|
|
260
|
+
Values such as:
|
|
261
|
+
|
|
262
|
+
```css
|
|
263
|
+
0.25em
|
|
264
|
+
1.5em
|
|
265
|
+
1lh
|
|
266
|
+
18rem
|
|
267
|
+
65ch
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
can be more stable and understandable than introducing a custom property or mapping them artificially onto the spacing scale.
|
|
271
|
+
|
|
272
|
+
Promote a value to a token when at least one of these becomes true:
|
|
273
|
+
|
|
274
|
+
* it represents a shared design decision
|
|
275
|
+
* multiple components need the same semantic value
|
|
276
|
+
* themes or density modes should modify it
|
|
277
|
+
* users are expected to configure it
|
|
278
|
+
* naming the value improves understanding of the API
|
|
279
|
+
|
|
280
|
+
Otherwise, keep the direct CSS unit.
|
|
281
|
+
|
|
282
|
+
## Guideline
|
|
283
|
+
|
|
284
|
+
> Use tokens for shared design rhythm. Use CSS relative units for intrinsic relationships.
|
|
285
|
+
|
|
286
|
+
Actual favors semantic CSS over tokenization for its own sake.
|