Mercurial
diff design_system/src/styles/components.css @ 258:60a876c4587a
[ui] Add semantic primitive ownership
Build a layered Zenbu token and sizing system, make authored controls use native-underneath primitives, migrate mrjunejune without imposing visual surfaces, and document/enforce HTML ownership in the catalog and wiki.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Wed, 05 Aug 2026 05:25:40 -0700 |
| parents | 2b6e732087ff |
| children |
line wrap: on
line diff
--- a/design_system/src/styles/components.css Tue Aug 04 16:49:11 2026 -0700 +++ b/design_system/src/styles/components.css Wed Aug 05 05:25:40 2026 -0700 @@ -1,4 +1,4 @@ -@import url("./shadcn.css"); +@import url("./elements.css"); :where( zen-button, @@ -9,78 +9,128 @@ zen-notifications ) { box-sizing: border-box; - font-family: var(--zen-font-sans); - color: var(--zen-color-text); + font-family: var(--zenbu-sys-font-family-ui); + color: var(--zenbu-sys-color-text-primary); } zen-button { display: inline-flex; } -zen-button > :where(button, a) { +zen-button:not([appearance="plain"]) > :where(button, a) { + --zenbu-button-background: + var(--zenbu-sys-color-action-primary-background); + --zenbu-button-background-hover: + var(--zenbu-sys-color-action-primary-background-hover); + --zenbu-button-background-pressed: + var(--zenbu-sys-color-action-primary-background-pressed); + --zenbu-button-border: + var(--zenbu-sys-color-action-primary-border); + --zenbu-button-foreground: + var(--zenbu-sys-color-action-primary-foreground); appearance: none; + box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; - gap: var(--zen-space-2); - min-height: 2.75rem; - padding: var(--zen-space-2) var(--zen-space-4); - border: var(--zen-border-width) solid var(--zen-color-border); - border-radius: var(--zen-radius-md); - box-shadow: var(--zen-shadow-sm); - background: var(--zen-color-accent); - color: var(--zen-color-on-accent); - font: 700 var(--zen-font-size-md)/1 var(--zen-font-sans); + gap: var(--zenbu-control-gap); + min-height: var(--zenbu-control-height); + padding: + var(--zenbu-control-padding-block) + var(--zenbu-control-padding-inline); + border: var(--zenbu-sys-stroke-width) solid var(--zenbu-button-border); + border-radius: var(--zenbu-sys-radius-control); + box-shadow: var(--zenbu-sys-shadow-subtle); + background: var(--zenbu-button-background); + color: var(--zenbu-button-foreground); + font: + var(--zenbu-sys-font-weight-strong) + var(--zenbu-control-font-size) / + var(--zenbu-control-line-height) + var(--zenbu-sys-font-family-ui); text-decoration: none; cursor: pointer; transition: - background var(--zen-motion-fast) ease, - box-shadow var(--zen-motion-fast) ease, - transform var(--zen-motion-fast) ease; + background var(--zenbu-sys-motion-duration-state) + var(--zenbu-sys-motion-ease-state), + border-color var(--zenbu-sys-motion-duration-state) + var(--zenbu-sys-motion-ease-state), + box-shadow var(--zenbu-sys-motion-duration-feedback) + var(--zenbu-sys-motion-ease-state), + transform var(--zenbu-sys-motion-duration-feedback) + var(--zenbu-sys-motion-ease-state); } -zen-button > :where(button, a):hover { - background: var(--zen-color-accent-hover); +zen-button:not([appearance="plain"]) > :where(button, a):hover:not( + :disabled, + [aria-disabled="true"] +) { + background: var(--zenbu-button-background-hover); transform: translate(-1px, -1px); - box-shadow: var(--zen-shadow-hover); + box-shadow: var(--zenbu-sys-shadow-raised); } -zen-button > :where(button, a):active { +zen-button:not([appearance="plain"]) > :where(button, a):active:not( + :disabled, + [aria-disabled="true"] +) { + background: var(--zenbu-button-background-pressed); transform: translate(2px, 2px); box-shadow: none; } -zen-button > :where(button, a):focus-visible, +zen-button:not([appearance="plain"]) > :where(button, a):focus-visible, zen-alert [data-zen-dismiss]:focus-visible { - outline: 3px solid var(--zen-color-focus); - outline-offset: 3px; + outline: var(--zenbu-sys-focus-width) solid + var(--zenbu-sys-color-focus-outer); + outline-offset: var(--zenbu-sys-focus-offset); } -zen-field :where(input, select, textarea):focus-visible { +zen-field:not([appearance="plain"]) :where( + input, + select, + textarea +):focus-visible { outline: none; } -zen-button[variant="quiet"] > :where(button, a) { - background: var(--zen-color-surface); - color: var(--zen-color-text); +zen-button:not([appearance="plain"])[variant="quiet"] > :where(button, a) { + --zenbu-button-background: + var(--zenbu-sys-color-action-neutral-background); + --zenbu-button-background-hover: + var(--zenbu-sys-color-action-neutral-background-hover); + --zenbu-button-background-pressed: + var(--zenbu-sys-color-action-neutral-background-pressed); + --zenbu-button-border: + var(--zenbu-sys-color-action-neutral-border); + --zenbu-button-foreground: + var(--zenbu-sys-color-action-neutral-foreground); } -zen-button[variant="danger"] > :where(button, a) { - background: var(--zen-color-danger); - color: var(--zen-color-on-danger); +zen-button:not([appearance="plain"])[variant="danger"] > :where(button, a) { + --zenbu-button-background: + var(--zenbu-sys-color-action-danger-background); + --zenbu-button-background-hover: + var(--zenbu-sys-color-action-danger-background-hover); + --zenbu-button-background-pressed: + var(--zenbu-sys-color-action-danger-background-pressed); + --zenbu-button-border: + var(--zenbu-sys-color-action-danger-border); + --zenbu-button-foreground: + var(--zenbu-sys-color-action-danger-foreground); } -zen-button[disabled] > :where(button, a), -zen-button[loading] > :where(button, a) { - opacity: 0.55; +zen-button:not([appearance="plain"])[disabled] > :where(button, a), +zen-button:not([appearance="plain"])[loading] > :where(button, a) { + opacity: var(--zenbu-sys-opacity-disabled); box-shadow: none; cursor: not-allowed; transform: none; } -zen-button[loading] > :where(button, a)::before { - width: 0.85rem; - height: 0.85rem; +zen-button:not([appearance="plain"])[loading] > :where(button, a)::before { + width: var(--zenbu-control-icon-size); + height: var(--zenbu-control-icon-size); border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; @@ -96,24 +146,30 @@ display: block; } -zen-card > :where(article, section) { +zen-card:not([appearance="plain"]) > :where(article, section) { + --zenbu-card-background: var(--zenbu-sys-color-surface-raised); + --zenbu-card-border: var(--zenbu-sys-color-border-subtle); + --zenbu-card-foreground: var(--zenbu-sys-color-text-primary); height: 100%; - padding: var(--zen-space-5); - border: var(--zen-border-width) solid var(--zen-color-border); - border-radius: var(--zen-radius-lg); - box-shadow: var(--zen-shadow-md); - background: var(--zen-color-surface-raised); + padding: var(--zenbu-sys-padding-lg); + border: var(--zenbu-sys-stroke-width) solid var(--zenbu-card-border); + border-radius: var(--zenbu-sys-radius-container); + box-shadow: var(--zenbu-sys-shadow-raised); + background: var(--zenbu-card-background); + color: var(--zenbu-card-foreground); } -zen-card[interactive] > :where(article, section) { +zen-card:not([appearance="plain"])[interactive] > :where(article, section) { transition: - box-shadow var(--zen-motion-fast) ease, - transform var(--zen-motion-fast) ease; + box-shadow var(--zenbu-sys-motion-duration-state) + var(--zenbu-sys-motion-ease-state), + transform var(--zenbu-sys-motion-duration-state) + var(--zenbu-sys-motion-ease-state); } -zen-card[interactive] > :where(article, section):hover { +zen-card:not([appearance="plain"])[interactive] > :where(article, section):hover { transform: translate(-2px, -2px); - box-shadow: var(--zen-shadow-lifted); + box-shadow: var(--zenbu-sys-shadow-overlay); } zen-card :where(h2, h3, p) { @@ -121,85 +177,123 @@ } zen-alert { + --zenbu-alert-background: var(--zenbu-sys-color-info-background); + --zenbu-alert-border: var(--zenbu-sys-color-info-border); + --zenbu-alert-foreground: var(--zenbu-sys-color-info-foreground); display: grid; - grid-template-columns: 0.35rem minmax(0, 1fr) auto; + --zenbu-alert-indicator-width: var(--zenbu-ref-space-1-5); + grid-template-columns: + var(--zenbu-alert-indicator-width) + minmax(0, 1fr) + auto; align-items: start; - gap: var(--zen-space-3); - padding: var(--zen-space-4); - border: var(--zen-border-width) solid var(--zen-color-border); - border-radius: var(--zen-radius-md); - background: var(--zen-color-surface-raised); + gap: var(--zenbu-sys-space-control); + padding: var(--zenbu-sys-padding-md); + border: var(--zenbu-sys-stroke-width) solid var(--zenbu-alert-border); + border-radius: var(--zenbu-sys-radius-container); + background: var(--zenbu-alert-background); + color: var(--zenbu-alert-foreground); } zen-alert::before { align-self: stretch; - border-radius: 999px; - background: var(--zen-color-info); + border-radius: var(--zenbu-sys-radius-pill); + background: var(--zenbu-alert-border); content: ""; } -zen-alert[tone="success"]::before { background: var(--zen-color-success); } -zen-alert[tone="warning"]::before { background: var(--zen-color-warning); } -zen-alert[tone="danger"]::before { background: var(--zen-color-danger); } +zen-alert[tone="success"] { + --zenbu-alert-background: var(--zenbu-sys-color-success-background); + --zenbu-alert-border: var(--zenbu-sys-color-success-border); + --zenbu-alert-foreground: var(--zenbu-sys-color-success-foreground); +} +zen-alert[tone="warning"] { + --zenbu-alert-background: var(--zenbu-sys-color-warning-background); + --zenbu-alert-border: var(--zenbu-sys-color-warning-border); + --zenbu-alert-foreground: var(--zenbu-sys-color-warning-foreground); +} +zen-alert[tone="danger"] { + --zenbu-alert-background: var(--zenbu-sys-color-danger-background); + --zenbu-alert-border: var(--zenbu-sys-color-danger-border); + --zenbu-alert-foreground: var(--zenbu-sys-color-danger-foreground); +} zen-alert > :where(p, div) { margin: 0; } zen-alert [data-zen-dismiss] { - min-width: 2rem; - min-height: 2rem; + min-width: var(--zenbu-sys-control-height-xs); + min-height: var(--zenbu-sys-control-height-xs); padding: 0; border: 0; - border-radius: 50%; + border-radius: var(--zenbu-sys-radius-pill); background: transparent; color: inherit; - font: 700 1.2rem/1 var(--zen-font-sans); + font: + var(--zenbu-sys-font-weight-bold) + var(--zenbu-sys-font-size-lg) / 1 + var(--zenbu-sys-font-family-ui); cursor: pointer; } zen-field { + --zenbu-field-textarea-min-height: + calc(var(--zenbu-ref-space-24) + var(--zenbu-ref-space-4)); display: grid; - gap: var(--zen-space-2); + gap: var(--zenbu-sys-space-icon-label); } -zen-field > label { - font-weight: 750; +zen-field[appearance="plain"] { + display: contents; +} + +zen-field:not([appearance="plain"]) > label { + font-family: var(--zenbu-sys-type-label-family); + font-size: var(--zenbu-sys-type-label-size); + font-weight: var(--zenbu-sys-type-label-weight); + line-height: var(--zenbu-sys-type-label-line-height); } -zen-field > :where(input, select, textarea) { +zen-field:not([appearance="plain"]) > :where(input, select, textarea) { width: 100%; - min-height: 2.75rem; + min-height: var(--zenbu-control-height); box-sizing: border-box; - padding: var(--zen-space-3); - border: var(--zen-border-width) solid var(--zen-color-border); - border-radius: var(--zen-radius-md); - background: var(--zen-color-surface); - color: var(--zen-color-text); - font: inherit; + padding: + var(--zenbu-control-padding-block) + var(--zenbu-control-padding-inline); + border: var(--zenbu-sys-stroke-width) solid + var(--zenbu-sys-color-border-default); + border-radius: var(--zenbu-sys-radius-control); + background: var(--zenbu-sys-color-surface-default); + color: var(--zenbu-sys-color-text-primary); + font-family: inherit; + font-size: var(--zenbu-control-font-size); + line-height: var(--zenbu-control-line-height); } -zen-field > textarea { - min-height: 7rem; +zen-field:not([appearance="plain"]) > textarea { + min-height: var(--zenbu-field-textarea-min-height); resize: vertical; } -zen-field > :where(small, [data-help]) { - color: var(--zen-color-text-muted); +zen-field:not([appearance="plain"]) > :where(small, [data-help]) { + color: var(--zenbu-sys-color-text-secondary); } -zen-field[data-invalid] > :where(input, select, textarea) { - border-color: var(--zen-color-danger); +zen-field:not([appearance="plain"])[data-invalid] > :where(input, select, textarea) { + border-color: var(--zenbu-sys-color-danger-border); } -zen-field[data-invalid] > :where(small, [data-help]) { - color: var(--zen-color-danger); +zen-field:not([appearance="plain"])[data-invalid] > :where(small, [data-help]) { + color: var(--zenbu-sys-color-danger-foreground); } zen-stack { + --zenbu-stack-gap: var(--zenbu-sys-space-group); display: flex; flex-direction: column; - gap: var(--zen-stack-gap, var(--zen-space-4)); + gap: var(--zenbu-stack-gap); } zen-stack[direction="row"] { @@ -207,59 +301,89 @@ align-items: center; } -zen-stack[gap="tight"] { --zen-stack-gap: var(--zen-space-2); } -zen-stack[gap="loose"] { --zen-stack-gap: var(--zen-space-6); } +zen-stack[gap="tight"] { + --zenbu-stack-gap: var(--zenbu-sys-space-related); +} +zen-stack[gap="loose"] { + --zenbu-stack-gap: var(--zenbu-sys-space-container); +} zen-notifications > [data-zen-notification-stack] { + --zenbu-notification-z-index: var(--zenbu-sys-z-notification); + --zenbu-notification-inset: var(--zenbu-sys-padding-md); + --zenbu-notification-bottom-inset: var(--zenbu-sys-padding-md); + --zenbu-notification-collapsed-height: 0; + --zenbu-notification-expanded-height: 0; position: fixed; - z-index: var(--zen-notification-z-index, 1000); - inset-inline-end: var(--zen-notification-inset, var(--zen-space-4)); + z-index: var( + --zenbu-notification-z-index, + var(--zenbu-sys-z-notification) + ); + inset-inline-end: var( + --zenbu-notification-inset, + var(--zenbu-sys-padding-md) + ); inset-block-end: var( - --zen-notification-bottom-inset, - var(--zen-space-4) + --zenbu-notification-bottom-inset, + var(--zenbu-sys-padding-md) ); inline-size: min(24rem, calc(100vi - 2rem)); - height: var(--zen-notification-collapsed-height, 0); - --zen-notification-gap: 12px; + height: var(--zenbu-notification-collapsed-height, 0); + --zenbu-notification-gap: var(--zenbu-sys-space-control); pointer-events: auto; isolation: isolate; - transition: height 220ms cubic-bezier(0.2, 0.8, 0.2, 1); + transition: + height var(--zenbu-sys-motion-duration-layout) + var(--zenbu-sys-motion-ease-layout); } zen-notifications > [data-zen-notification-stack]:is( :hover, :focus-within ) { - height: var(--zen-notification-expanded-height, 0); + height: var(--zenbu-notification-expanded-height, 0); } zen-notifications > [data-zen-notification-stack] > article { + --zenbu-notification-collapsed-opacity: 1; + --zenbu-notification-collapsed-offset: 0; + --zenbu-notification-offset: 0; + --zenbu-notification-scale: 1; + --zenbu-notification-background: + var(--zenbu-sys-color-surface-raised); + --zenbu-notification-border: + var(--zenbu-sys-color-border-subtle); + --zenbu-notification-foreground: + var(--zenbu-sys-color-text-primary); position: absolute; inset-inline: 0; inset-block-end: 0; display: grid; grid-template-columns: 1.75rem minmax(0, 1fr) 2rem; align-items: start; - gap: var(--zen-space-3); - padding: var(--zen-space-4); - border: var(--zen-border-width) solid var(--zen-color-border); - border-radius: var(--zen-radius-lg); - box-shadow: var(--zen-shadow-md); - background: var(--zen-color-surface-raised); - color: var(--zen-color-text); - opacity: var(--zen-notification-collapsed-opacity, 1); + gap: var(--zenbu-sys-space-control); + padding: var(--zenbu-sys-padding-md); + border: var(--zenbu-sys-stroke-width) solid + var(--zenbu-notification-border); + border-radius: var(--zenbu-sys-radius-container); + box-shadow: var(--zenbu-sys-shadow-overlay); + background: var(--zenbu-notification-background); + color: var(--zenbu-notification-foreground); + opacity: var(--zenbu-notification-collapsed-opacity, 1); pointer-events: none; transform: translate3d( 0, - var(--zen-notification-collapsed-offset, 0), + var(--zenbu-notification-collapsed-offset, 0), 0 ) - scale(var(--zen-notification-scale, 1)); + scale(var(--zenbu-notification-scale, 1)); transform-origin: center bottom; transition: - opacity 180ms ease, - transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); + opacity var(--zenbu-sys-motion-duration-exit) + var(--zenbu-sys-motion-ease-exit), + transform var(--zenbu-sys-motion-duration-layout) + var(--zenbu-sys-motion-ease-layout); will-change: transform; animation: zen-notification-enter 160ms ease-out; } @@ -277,7 +401,7 @@ transform: translate3d( 0, - var(--zen-notification-offset, 0px), + var(--zenbu-notification-offset, 0px), 0 ) scale(1); @@ -290,34 +414,38 @@ } .zen-notification-tone { + --zenbu-notification-tone-background: + var(--zenbu-sys-color-info-foreground); + --zenbu-notification-tone-foreground: + var(--zenbu-sys-color-text-inverse); display: grid; width: 1.75rem; height: 1.75rem; place-items: center; - border-radius: 50%; - background: var(--zen-color-info); - color: var(--zen-color-on-info); - font-weight: 900; + border-radius: var(--zenbu-sys-radius-pill); + background: var(--zenbu-notification-tone-background); + color: var(--zenbu-notification-tone-foreground); + font-weight: var(--zenbu-sys-font-weight-bold); } [data-tone="success"] > .zen-notification-tone { - background: var(--zen-color-success); - color: var(--zen-color-on-success); + --zenbu-notification-tone-background: + var(--zenbu-sys-color-success-foreground); } [data-tone="warning"] > .zen-notification-tone { - background: var(--zen-color-warning); - color: var(--zen-color-on-warning); + --zenbu-notification-tone-background: + var(--zenbu-sys-color-warning-foreground); } [data-tone="error"] > .zen-notification-tone { - background: var(--zen-color-danger); - color: var(--zen-color-on-danger); + --zenbu-notification-tone-background: + var(--zenbu-sys-color-danger-foreground); } .zen-notification-content { display: grid; - gap: var(--zen-space-1); + gap: var(--zenbu-sys-space-icon-label); } .zen-notification-message, @@ -326,12 +454,12 @@ } .zen-notification-message { - font-weight: 800; + font-weight: var(--zenbu-sys-font-weight-bold); } .zen-notification-description { - color: var(--zen-color-text-muted); - font-size: var(--zen-font-size-sm); + color: var(--zenbu-sys-color-text-secondary); + font-size: var(--zenbu-sys-font-size-sm); } .zen-notification-action, @@ -344,10 +472,10 @@ .zen-notification-action { justify-self: start; - margin-top: var(--zen-space-2); + margin-top: var(--zenbu-sys-space-related); padding: 0; - color: var(--zen-color-info); - font-weight: 800; + color: var(--zenbu-sys-color-text-link); + font-weight: var(--zenbu-sys-font-weight-bold); text-decoration: underline; text-underline-offset: 0.2em; } @@ -356,14 +484,15 @@ width: 2rem; height: 2rem; padding: 0; - border-radius: 50%; - font-size: 1.25rem; + border-radius: var(--zenbu-sys-radius-pill); + font-size: var(--zenbu-sys-font-size-xl); } .zen-notification-action:focus-visible, .zen-notification-dismiss:focus-visible { - outline: 3px solid var(--zen-color-focus); - outline-offset: 2px; + outline: var(--zenbu-sys-focus-width) solid + var(--zenbu-sys-color-focus-outer); + outline-offset: var(--zenbu-sys-focus-offset); } .zen-visually-hidden {