diff --git a/.changeset/mosaic-gray-scale.md b/.changeset/mosaic-gray-scale.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/mosaic-gray-scale.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/mosaic/references/stylex.md b/.claude/skills/mosaic/references/stylex.md index 47c03e5e9fb..abf9e762a9c 100644 --- a/.claude/skills/mosaic/references/stylex.md +++ b/.claude/skills/mosaic/references/stylex.md @@ -70,7 +70,7 @@ it gives consumers stable `--cl-*` vars to override in plain CSS: const colorDefaults = { // one value carries light + dark; resolves against the in-scope `color-scheme`, // so dark mode lives in the token, no `@media (prefers-color-scheme)` copy. - '--cl-color-primary': 'light-dark(oklch(0.205 0 0), oklch(0.922 0 0))', + '--cl-color-brand': 'light-dark(oklch(0.2046 0 0), oklch(0.9851 0 0))', } as const; export const colorVars = stylex.defineVars(colorDefaults); ``` @@ -78,6 +78,9 @@ export const colorVars = stylex.defineVars(colorDefaults); - **DO** put light + dark in one token value via `light-dark()`. Never ship a second `@media (prefers-color-scheme: dark)` copy of a color. - **DO** reserve `--cl-*`-prefixed keys for the public, overridable contract. +- **DO** derive a gray-backed colour token from `--cl-color-neutral` mixed into + `--cl-color-background` (`neutralMix(80, 98)` in `tokens.stylex.ts`) rather than + restating an oklch value. There is no gray scale: retinting neutral retints the ramp. - **DO** name internal, non-contract vars with a `--_cl-*` prefix (e.g. a value a parent writes for a child to read). They still emit verbatim but the `_` marks them "not a contract, don't override." @@ -87,7 +90,7 @@ export const colorVars = stylex.defineVars(colorDefaults); wrapping controls. A role name survives a value change; `--cl-radius-md` doesn't. - **DO** compute tints at the call site with `color-mix()`, not as their own tokens. `color-mix(in oklab, ${primary}, ${fg} 12%)` beats minting - `--cl-color-primary-hover-12`. + `--cl-color-brand-hover-12`. - **DON'T** mint a per-step derivative token for something a `calc()`/`color-mix()` can express from an existing token. - **DON'T** give one value two public names. The focus ring's colour is @@ -95,14 +98,16 @@ export const colorVars = stylex.defineVars(colorDefaults); would let a consumer override one and not the other, and the ring's appearance would then depend on which they picked. - **DO** build a fill that sits _on top of_ an unknown backdrop — a hover or pressed - wash on a transparent `outline`/`ghost` control — as a **scrim**: an opacity of - black-on-light / white-on-dark over `transparent`, not a percentage of a gray token. + wash on a transparent `outline`/`ghost` control — from the `--cl-color-neutral-alpha-*` + tokens: `--cl-color-neutral` (black on light, white on dark) at a fixed opacity over + `transparent`, not a percentage of a gray token. Status colors have the same three + steps (`--cl-color-negative-alpha-100` …). ```ts - const step = `color-mix(in oklab, light-dark(oklch(0 0 0), oklch(1 0 0)) 12%, transparent)`; + backgroundColor: colorVars['--cl-color-neutral-alpha-300'], ``` - A gray token like `--cl-color-neutral` is a 900, not black, so the same percentage of + A gray token like `--cl-color-foreground` is a 900, not black, so the same percentage of it lands lighter than the percentage of black — and by an amount that shifts with whatever the control sits on, so the step numbers stop describing what they render. The scrim composites, so one ramp reads consistently on every surface. This applies @@ -128,7 +133,7 @@ imported file"). `defineConsts` is StyleX's shareable inlined-value primitive, s it's the only way to get a scale that is both shared across components and free of per-step vars. -**Reference tokens by bracket string key**, always: `colorVars['--cl-color-primary']`, +**Reference tokens by bracket string key**, always: `colorVars['--cl-color-brand']`, `space['2']`. A computed key (`colorVars[name]`) defeats StyleX static analysis and won't compile. @@ -157,7 +162,7 @@ objects and compose them at the call site. }); // variant map: keyed by the prop value, indexed at the call site const variants = stylex.create({ - primary: { backgroundColor: colorVars['--cl-color-primary'] }, + primary: { backgroundColor: colorVars['--cl-color-brand'] }, secondary: { backgroundColor: colorVars['--cl-color-secondary'] }, }); const sizes = stylex.create({ @@ -232,7 +237,7 @@ instead of 12, with each axis staying independent. **Don't.** inlines it at build, so the duplication leaves the source without emitting a var: ```ts - const primaryHover = `color-mix(in oklab, ${colorVars['--cl-color-primary']}, ${colorVars['--cl-color-primary-foreground']} 12%)`; + const primaryHover = `color-mix(in oklab, ${colorVars['--cl-color-brand']}, ${colorVars['--cl-color-brand-foreground']} 12%)`; ``` Same-file is required — an imported one fails static evaluation ("Atoms" above). @@ -255,7 +260,7 @@ Use StyleX's conditional-value objects (a `default` plus pseudo / at-rule keys). ```ts backgroundColor: { - default: colorVars['--cl-color-primary'], + default: colorVars['--cl-color-brand'], ':active': primaryActive, '@media (hover: hover)': { // the media block contributes only the pseudo; the top-level `default` still @@ -312,11 +317,11 @@ device, while touch devices look correct. ```ts backgroundColor: { default: 'transparent', - ':enabled:active': neutralStep1, - ':enabled[data-open]': neutralStep1, + ':enabled:active': colorVars['--cl-color-neutral-alpha-200'], + ':enabled[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':enabled:hover:not(:active):not([data-open])': neutralStep0, + ':enabled:hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, ``` diff --git a/packages/swingset/src/stories/banner.mdx b/packages/swingset/src/stories/banner.mdx index ff38bc97db9..09bf18b40f4 100644 --- a/packages/swingset/src/stories/banner.mdx +++ b/packages/swingset/src/stories/banner.mdx @@ -58,7 +58,7 @@ Each part carries its stable slot class alongside the generated StyleX atoms and | `Banner.Label` | `neutral` \| `warning` \| `negative` | | `Banner.Description` | `neutral` \| `warning` \| `negative` | -The fill is a 4% mix of the color's token rather than its `-faded` surface, so a banner tints whatever it sits on instead of painting over it, and it inverts with the token in dark mode. Retheme a color by overriding the token it reads — `--cl-color-negative`, `--cl-color-warning`, or `--cl-color-neutral` (plus `--cl-color-border`, which draws the neutral hairline) — and the fill, border, icon, and copy all move together. +Warning and negative paint the color's `-subtle` surface inside its `-border` hairline and use the saturated token for the icon and copy, so retheme one by overriding `--cl-color-negative-subtle`, `--cl-color-negative-border`, and `--cl-color-negative` (or the `warning` equivalents). Neutral has no subtle surface of its own: its fill is `--cl-color-neutral-alpha-100`, a 6% wash of `--cl-color-neutral` over whatever it sits on, and `--cl-color-border` draws its hairline. --- diff --git a/packages/swingset/src/stories/icon-frame.mdx b/packages/swingset/src/stories/icon-frame.mdx index ec98733d3b8..6eb7c679ada 100644 --- a/packages/swingset/src/stories/icon-frame.mdx +++ b/packages/swingset/src/stories/icon-frame.mdx @@ -2,7 +2,7 @@ import * as IconFrameStories from './icon-frame.stories'; # IconFrame -IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 40px frame; the filled treatment uses `oklch(0.9702 0 0)` in light mode and `oklch(0.2393 0 0)` in dark mode. +IconFrame centers an icon or other visual in a square Mosaic surface. `bordered` and `filled` are independent treatments, while `size` controls the frame dimensions. The default is a bordered 40px frame; the filled treatment uses `--cl-color-background-subtle`. ## Playground diff --git a/packages/swingset/src/stories/icon-frame.stories.tsx b/packages/swingset/src/stories/icon-frame.stories.tsx index 2242150d5fe..835ec7f3233 100644 --- a/packages/swingset/src/stories/icon-frame.stories.tsx +++ b/packages/swingset/src/stories/icon-frame.stories.tsx @@ -9,8 +9,8 @@ export { default as __source } from './icon-frame.stories?raw'; const styles = stylex.create({ customSurface: { - backgroundColor: colorVars['--cl-color-primary'], - color: colorVars['--cl-color-primary-foreground'], + backgroundColor: colorVars['--cl-color-brand'], + color: colorVars['--cl-color-brand-foreground'], }, }); diff --git a/packages/swingset/src/stories/item.mdx b/packages/swingset/src/stories/item.mdx index 3dc335f555f..b5f18f87c0c 100644 --- a/packages/swingset/src/stories/item.mdx +++ b/packages/swingset/src/stories/item.mdx @@ -166,7 +166,7 @@ The root reflects its state as `data-*` attributes on `.cl-item`, so consumers c ```css /* Re-theme interactive rows */ .cl-item[data-interactive] { - background-color: var(--cl-color-card); + background-color: var(--cl-color-background); } /* Widen the media column on compact rows */ diff --git a/packages/swingset/src/stories/scroll-area.mdx b/packages/swingset/src/stories/scroll-area.mdx index 61101e5a27a..f6faf26245d 100644 --- a/packages/swingset/src/stories/scroll-area.mdx +++ b/packages/swingset/src/stories/scroll-area.mdx @@ -154,13 +154,13 @@ can drive itself from them. } .cl-item-group::before { top: 0; - background: linear-gradient(to bottom, color-mix(in oklab, var(--cl-color-card-foreground) 22%, transparent), transparent); + background: linear-gradient(to bottom, color-mix(in oklab, var(--cl-color-foreground) 22%, transparent), transparent); opacity: var(--cl-scroll-area-progress-start); transform: translateY(calc((var(--cl-scroll-area-progress-start) - 1) * var(--cl-scroll-fade-size))); } .cl-item-group::after { bottom: 0; - background: linear-gradient(to top, color-mix(in oklab, var(--cl-color-card-foreground) 22%, transparent), transparent); + background: linear-gradient(to top, color-mix(in oklab, var(--cl-color-foreground) 22%, transparent), transparent); opacity: var(--cl-scroll-area-progress-end); transform: translateY(calc((1 - var(--cl-scroll-area-progress-end)) * var(--cl-scroll-fade-size))); } @@ -170,7 +170,7 @@ The vars are registered as ``, so they drive position as readily as opac slides out from behind its own edge as it fades in. Clip the root — `overflow: hidden` — so the half that is still offscreen stays there. -Mix the scrim from a theme colour rather than hardcoding black: `--cl-color-card-foreground` inverts +Mix the scrim from a theme colour rather than hardcoding black: `--cl-color-foreground` inverts with the theme, so one declaration reads as a shadow on light and a soft glow on dark, where black would vanish. diff --git a/packages/swingset/src/stories/scroll-area.stories.tsx b/packages/swingset/src/stories/scroll-area.stories.tsx index cca746f5f27..ea3b25f28b0 100644 --- a/packages/swingset/src/stories/scroll-area.stories.tsx +++ b/packages/swingset/src/stories/scroll-area.stories.tsx @@ -283,7 +283,7 @@ export function ThemedScrollbar() { /** * The mask retired for overlay scrims, each reading the progress var for its edge. The scrim mixes - * from `--cl-color-card-foreground`, so it reads as a shadow on light and a glow on dark; + * from `--cl-color-foreground`, so it reads as a shadow on light and a glow on dark; * hardcoded black would vanish on a dark surface. `overflow: hidden` on the root keeps the scrims * inside its rounded corners. * @@ -311,13 +311,13 @@ export function ShadowIndicators() { } .demo-scroll-shadows .cl-item-group::before { top: 0; - background: linear-gradient(to bottom, color-mix(in oklab, var(--cl-color-card-foreground) 22%, transparent), transparent); + background: linear-gradient(to bottom, color-mix(in oklab, var(--cl-color-foreground) 22%, transparent), transparent); opacity: var(--cl-scroll-area-progress-start); transform: translateY(calc((var(--cl-scroll-area-progress-start) - 1) * var(--cl-scroll-fade-size))); } .demo-scroll-shadows .cl-item-group::after { bottom: 0; - background: linear-gradient(to top, color-mix(in oklab, var(--cl-color-card-foreground) 22%, transparent), transparent); + background: linear-gradient(to top, color-mix(in oklab, var(--cl-color-foreground) 22%, transparent), transparent); opacity: var(--cl-scroll-area-progress-end); transform: translateY(calc((1 - var(--cl-scroll-area-progress-end)) * var(--cl-scroll-fade-size))); } diff --git a/packages/swingset/src/stories/select.component.stories.tsx b/packages/swingset/src/stories/select.component.stories.tsx index d0afaeac26e..ac71b96649c 100644 --- a/packages/swingset/src/stories/select.component.stories.tsx +++ b/packages/swingset/src/stories/select.component.stories.tsx @@ -80,7 +80,7 @@ export function Ghost() { alignItems: 'center', gap: 16, paddingBlock: 8, - borderBlockEnd: '1px solid var(--cl-color-border-faded)', + borderBlockEnd: '1px solid var(--cl-color-border-subtle)', }} >
diff --git a/packages/ui/src/mosaic/components/avatar/avatar.styles.ts b/packages/ui/src/mosaic/components/avatar/avatar.styles.ts index 513c962a570..cc61b1674ea 100644 --- a/packages/ui/src/mosaic/components/avatar/avatar.styles.ts +++ b/packages/ui/src/mosaic/components/avatar/avatar.styles.ts @@ -50,8 +50,8 @@ export const styles = stylex.create({ fallback: { borderRadius: 'inherit', alignItems: 'center', - backgroundColor: `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 8%, transparent)`, - color: colorVars['--cl-color-neutral'], + backgroundColor: colorVars['--cl-color-neutral-alpha-200'], + color: colorVars['--cl-color-foreground'], display: 'flex', justifyContent: 'center', height: '100%', @@ -81,7 +81,7 @@ export const styles = stylex.create({ borderWidth: '1px', overflow: 'hidden', alignItems: 'center', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], boxSizing: 'border-box', display: 'flex', insetBlockEnd: `calc(${space['2']} * -1)`, diff --git a/packages/ui/src/mosaic/components/badge/badge.styles.ts b/packages/ui/src/mosaic/components/badge/badge.styles.ts index 6e2580d412d..79c83f46dd2 100644 --- a/packages/ui/src/mosaic/components/badge/badge.styles.ts +++ b/packages/ui/src/mosaic/components/badge/badge.styles.ts @@ -2,14 +2,12 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, fontFamilyVars, fontWeightVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; -// warning/negative/positive tint a faded fill and use the saturated token as text; +// warning/negative/positive fill with the subtle surface and use the saturated token as text; // primary fills with the solid token and uses its `-foreground` for text. // -// Neutral has no faded surface to tint — `--cl-color-neutral-faded` is a text gray, and its +// Neutral has no subtle surface to fill — `--cl-color-foreground-secondary` is a text gray, and its // `-foreground` is a text color rather than an on-fill one, so it is unreadable against the solid -// 900. It rides the same black/white scrim the button's neutral fill does, which composites against -// any backdrop. Must be a local binding — StyleX inlines it; an imported one fails to compile. -const neutralScrim = `color-mix(in oklab, light-dark(oklch(0 0 0), oklch(1 0 0)) 6%, transparent)`; +// 900. It rides the same neutral scrim the button's neutral fill does. export const styles = stylex.create({ base: { borderRadius: radiusVars['--cl-radius-full'], @@ -29,23 +27,23 @@ export const styles = stylex.create({ export const colors = stylex.create({ primary: { - backgroundColor: colorVars['--cl-color-primary'], - color: colorVars['--cl-color-primary-foreground'], + backgroundColor: colorVars['--cl-color-brand'], + color: colorVars['--cl-color-brand-foreground'], }, neutral: { - backgroundColor: neutralScrim, - color: colorVars['--cl-color-neutral-foreground'], + backgroundColor: colorVars['--cl-color-neutral-alpha-100'], + color: colorVars['--cl-color-foreground'], }, warning: { - backgroundColor: colorVars['--cl-color-warning-faded'], + backgroundColor: colorVars['--cl-color-warning-subtle'], color: colorVars['--cl-color-warning'], }, negative: { - backgroundColor: colorVars['--cl-color-negative-faded'], + backgroundColor: colorVars['--cl-color-negative-subtle'], color: colorVars['--cl-color-negative'], }, positive: { - backgroundColor: colorVars['--cl-color-positive-faded'], + backgroundColor: colorVars['--cl-color-positive-subtle'], color: colorVars['--cl-color-positive'], }, }); diff --git a/packages/ui/src/mosaic/components/banner/banner.styles.ts b/packages/ui/src/mosaic/components/banner/banner.styles.ts index 0e7c1067bec..9f7c15cc42c 100644 --- a/packages/ui/src/mosaic/components/banner/banner.styles.ts +++ b/packages/ui/src/mosaic/components/banner/banner.styles.ts @@ -2,12 +2,6 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, fontFamilyVars, fontWeightVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; -const neutralFill = `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`; -const warningFill = `color-mix(in oklab, ${colorVars['--cl-color-warning']} 4%, transparent)`; -const negativeFill = `color-mix(in oklab, ${colorVars['--cl-color-negative']} 4%, transparent)`; -const warningBorder = `color-mix(in oklab, ${colorVars['--cl-color-warning']} 20%, transparent)`; -const negativeBorder = `color-mix(in oklab, ${colorVars['--cl-color-negative']} 20%, transparent)`; - export const styles = stylex.create({ root: { borderRadius: radiusVars['--cl-radius-lg'], @@ -43,23 +37,23 @@ export const styles = stylex.create({ export const rootColors = stylex.create({ neutral: { borderColor: colorVars['--cl-color-border'], - backgroundColor: neutralFill, - color: colorVars['--cl-color-neutral-foreground'], + backgroundColor: colorVars['--cl-color-neutral-alpha-100'], + color: colorVars['--cl-color-foreground'], }, warning: { - borderColor: warningBorder, - backgroundColor: warningFill, + borderColor: colorVars['--cl-color-warning-border'], + backgroundColor: colorVars['--cl-color-warning-subtle'], color: colorVars['--cl-color-warning'], }, negative: { - borderColor: negativeBorder, - backgroundColor: negativeFill, + borderColor: colorVars['--cl-color-negative-border'], + backgroundColor: colorVars['--cl-color-negative-subtle'], color: colorVars['--cl-color-negative'], }, }); export const descriptionColors = stylex.create({ - neutral: { color: colorVars['--cl-color-neutral-faded'] }, + neutral: { color: colorVars['--cl-color-foreground-secondary'] }, warning: { color: colorVars['--cl-color-warning'] }, negative: { color: colorVars['--cl-color-negative'] }, }); diff --git a/packages/ui/src/mosaic/components/branding/branding.styles.ts b/packages/ui/src/mosaic/components/branding/branding.styles.ts index 41e2afd7b7e..3a107de0a5b 100644 --- a/packages/ui/src/mosaic/components/branding/branding.styles.ts +++ b/packages/ui/src/mosaic/components/branding/branding.styles.ts @@ -5,7 +5,7 @@ import { colorVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex export const styles = stylex.create({ // The mark only: text and link. Where it sits (a card's foot, a sidebar's) is the host's call. base: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], display: 'inline-block', fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], diff --git a/packages/ui/src/mosaic/components/button/button.styles.ts b/packages/ui/src/mosaic/components/button/button.styles.ts index 24e85561a71..02a76ba58b6 100644 --- a/packages/ui/src/mosaic/components/button/button.styles.ts +++ b/packages/ui/src/mosaic/components/button/button.styles.ts @@ -12,28 +12,15 @@ import { } from '../../tokens.stylex'; import { iconScope } from '../icon/icon.markers.stylex'; -// Neutral fills are three steps up one gray ramp, shared across variants: -// step 0 — `filled-neutral` rest, `outline-*`/`ghost-*` hover -// step 1 — `filled-neutral` hover, `outline-*`/`ghost-*` pressed -// step 2 — `filled-neutral` pressed -// -// Scrim opacities over `transparent`, so they composite against any backdrop. Not -// `--cl-color-neutral`: it's a 900, so a percentage of it lands lighter than the same -// percentage of black by an amount that shifts with the backdrop. -// -// TODO: codify this %-mix scale as shared tokens once more components adopt these -// shades — `item` and `avatar` still mix from `--cl-color-neutral`, and unifying them -// is a palette-wide change. -// -// Must be local bindings — StyleX inlines them; an imported one fails to compile. -const neutralStep0 = `color-mix(in oklab, light-dark(oklch(0 0 0), oklch(1 0 0)) 6%, transparent)`; -const neutralStep1 = `color-mix(in oklab, light-dark(oklch(0 0 0), oklch(1 0 0)) 12%, transparent)`; -const neutralStep2 = `color-mix(in oklab, light-dark(oklch(0 0 0), oklch(1 0 0)) 18%, transparent)`; +// Neutral fills are the three neutral alpha steps, shared across variants: +// 100 — `filled-neutral` rest, `outline-*`/`ghost-*` hover +// 200 — `filled-neutral` hover, `outline-*`/`ghost-*` pressed +// 300 — `filled-neutral` pressed // Opaque fills blend toward their own on-fill at the same 12%/18%. Neutral has no fill // to blend from, so it rides the opacity ramp above. -const primaryHover = `color-mix(in oklab, ${colorVars['--cl-color-primary']}, ${colorVars['--cl-color-primary-foreground']} 12%)`; -const primaryActive = `color-mix(in oklab, ${colorVars['--cl-color-primary']}, ${colorVars['--cl-color-primary-foreground']} 18%)`; +const primaryHover = `color-mix(in oklab, ${colorVars['--cl-color-brand']}, ${colorVars['--cl-color-brand-foreground']} 12%)`; +const primaryActive = `color-mix(in oklab, ${colorVars['--cl-color-brand']}, ${colorVars['--cl-color-brand-foreground']} 18%)`; const negativeHover = `color-mix(in oklab, ${colorVars['--cl-color-negative']}, ${colorVars['--cl-color-negative-foreground']} 12%)`; const negativeActive = `color-mix(in oklab, ${colorVars['--cl-color-negative']}, ${colorVars['--cl-color-negative-foreground']} 18%)`; @@ -44,9 +31,9 @@ const negativeActive = `color-mix(in oklab, ${colorVars['--cl-color-negative']}, // The achromatic foregrounds (`primary`, `neutral-foreground`) share the house faded gray. The two // that carry hue fade toward the tint that belongs to them, and the light-on-fill pairs fade toward // their own fill, which is the only backdrop they can ever sit on. -const iconFadedNeutral = colorVars['--cl-color-neutral-faded']; -const iconFadedNegative = `color-mix(in oklab, ${colorVars['--cl-color-negative']}, ${colorVars['--cl-color-negative-faded']} 50%)`; -const iconFadedOnPrimary = `color-mix(in oklab, ${colorVars['--cl-color-primary-foreground']}, ${colorVars['--cl-color-primary']} 40%)`; +const iconFadedNeutral = colorVars['--cl-color-foreground-secondary']; +const iconFadedNegative = `color-mix(in oklab, ${colorVars['--cl-color-negative']}, ${colorVars['--cl-color-negative-subtle']} 50%)`; +const iconFadedOnPrimary = `color-mix(in oklab, ${colorVars['--cl-color-brand-foreground']}, ${colorVars['--cl-color-brand']} 40%)`; const iconFadedOnNegative = `color-mix(in oklab, ${colorVars['--cl-color-negative-foreground']}, ${colorVars['--cl-color-negative']} 40%)`; // Interactive states are gated on `:not([data-disabled])`: the button stays hit-testable while @@ -183,14 +170,14 @@ export const variants = stylex.create({ 'filled-primary': { '--_cl-icon-color': { default: iconFadedOnPrimary, - ':not([data-disabled])[data-open]': colorVars['--cl-color-primary-foreground'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-brand-foreground'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-primary-foreground'], + ':not([data-disabled]):hover': colorVars['--cl-color-brand-foreground'], }, }, backgroundColor: { - default: colorVars['--cl-color-primary'], + default: colorVars['--cl-color-brand'], ':not([data-disabled]):not([data-pending]):active': primaryActive, ':not([data-disabled])[data-open]': primaryActive, '@media (hover: hover)': { @@ -198,27 +185,27 @@ export const variants = stylex.create({ ':not([data-disabled]):hover:not(:active):not([data-open])': primaryHover, }, }, - color: colorVars['--cl-color-primary-foreground'], + color: colorVars['--cl-color-brand-foreground'], }, 'filled-neutral': { '--_cl-icon-color': { default: iconFadedNeutral, - ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-foreground'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled]):hover': colorVars['--cl-color-foreground'], }, }, backgroundColor: { - default: neutralStep0, - ':not([data-disabled]):not([data-pending]):active': neutralStep2, - ':not([data-disabled])[data-open]': neutralStep2, + default: colorVars['--cl-color-neutral-alpha-100'], + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-300'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-300'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep1, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-200'], }, }, - color: colorVars['--cl-color-neutral-foreground'], + color: colorVars['--cl-color-foreground'], }, 'filled-negative': { '--_cl-icon-color': { @@ -247,44 +234,44 @@ export const variants = stylex.create({ 'outline-primary': { '--_cl-icon-color': { default: iconFadedNeutral, - ':not([data-disabled])[data-open]': colorVars['--cl-color-primary'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-brand'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-primary'], + ':not([data-disabled]):hover': colorVars['--cl-color-brand'], }, }, borderColor: colorVars['--cl-color-border'], backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': neutralStep1, - ':not([data-disabled])[data-open]': neutralStep1, + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-200'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep0, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], }, 'outline-neutral': { '--_cl-icon-color': { default: iconFadedNeutral, - ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-foreground'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled]):hover': colorVars['--cl-color-foreground'], }, }, borderColor: colorVars['--cl-color-border'], backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': neutralStep1, - ':not([data-disabled])[data-open]': neutralStep1, + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-200'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep0, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, - color: colorVars['--cl-color-neutral-foreground'], + color: colorVars['--cl-color-foreground'], }, 'outline-negative': { '--_cl-icon-color': { @@ -298,11 +285,11 @@ export const variants = stylex.create({ borderColor: colorVars['--cl-color-border'], backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': neutralStep1, - ':not([data-disabled])[data-open]': neutralStep1, + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-200'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep0, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, color: colorVars['--cl-color-negative'], @@ -311,42 +298,42 @@ export const variants = stylex.create({ 'ghost-primary': { '--_cl-icon-color': { default: iconFadedNeutral, - ':not([data-disabled])[data-open]': colorVars['--cl-color-primary'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-brand'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-primary'], + ':not([data-disabled]):hover': colorVars['--cl-color-brand'], }, }, backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': neutralStep1, - ':not([data-disabled])[data-open]': neutralStep1, + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-200'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep0, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], }, 'ghost-neutral': { '--_cl-icon-color': { default: iconFadedNeutral, - ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-foreground'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled]):hover': colorVars['--cl-color-foreground'], }, }, backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': neutralStep1, - ':not([data-disabled])[data-open]': neutralStep1, + ':not([data-disabled]):not([data-pending]):active': colorVars['--cl-color-neutral-alpha-200'], + ':not([data-disabled])[data-open]': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': neutralStep0, + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-neutral-alpha-100'], }, }, - color: colorVars['--cl-color-neutral-foreground'], + color: colorVars['--cl-color-foreground'], }, // The one ghost that tints instead of graying, so its pressed step walks its own faded // fill toward the negative it carries rather than joining the gray ramp. @@ -361,11 +348,11 @@ export const variants = stylex.create({ }, backgroundColor: { default: 'transparent', - ':not([data-disabled]):not([data-pending]):active': `color-mix(in oklab, ${colorVars['--cl-color-negative-faded']}, ${colorVars['--cl-color-negative']} 8%)`, - ':not([data-disabled])[data-open]': `color-mix(in oklab, ${colorVars['--cl-color-negative-faded']}, ${colorVars['--cl-color-negative']} 8%)`, + ':not([data-disabled]):not([data-pending]):active': `color-mix(in oklab, ${colorVars['--cl-color-negative-subtle']}, ${colorVars['--cl-color-negative']} 8%)`, + ':not([data-disabled])[data-open]': `color-mix(in oklab, ${colorVars['--cl-color-negative-subtle']}, ${colorVars['--cl-color-negative']} 8%)`, '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-negative-faded'], + ':not([data-disabled]):hover:not(:active):not([data-open])': colorVars['--cl-color-negative-subtle'], }, }, color: colorVars['--cl-color-negative'], @@ -382,11 +369,11 @@ export const variants = stylex.create({ default: iconFadedNeutral, '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-primary'], + ':not([data-disabled]):hover': colorVars['--cl-color-brand'], }, }, backgroundColor: 'transparent', - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], paddingInlineEnd: 0, paddingInlineStart: 0, textDecorationColor: { default: 'transparent', ':not([data-disabled]):hover': 'currentColor' }, @@ -399,11 +386,11 @@ export const variants = stylex.create({ default: iconFadedNeutral, '@media (hover: hover)': { default: null, - ':not([data-disabled]):hover': colorVars['--cl-color-neutral-foreground'], + ':not([data-disabled]):hover': colorVars['--cl-color-foreground'], }, }, backgroundColor: 'transparent', - color: colorVars['--cl-color-neutral-foreground'], + color: colorVars['--cl-color-foreground'], paddingInlineEnd: 0, paddingInlineStart: 0, textDecorationColor: { default: 'transparent', ':not([data-disabled]):hover': 'currentColor' }, diff --git a/packages/ui/src/mosaic/components/card/card.styles.ts b/packages/ui/src/mosaic/components/card/card.styles.ts index f762aa48522..11a696bd8c3 100644 --- a/packages/ui/src/mosaic/components/card/card.styles.ts +++ b/packages/ui/src/mosaic/components/card/card.styles.ts @@ -7,7 +7,7 @@ const compactCard = '@container card (max-width: 20rem)' as const; export const root = stylex.create({ base: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], containerName: 'card', containerType: 'inline-size', display: 'flex', @@ -18,8 +18,8 @@ export const root = stylex.create({ card: { borderRadius: radiusVars['--cl-radius-xl'], overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: shadowVars['--cl-shadow-card'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-lg'], }, flush: { borderRadius: radiusVars['--cl-radius-xl'], @@ -30,8 +30,8 @@ export const root = stylex.create({ overlay: { borderRadius: radiusVars['--cl-radius-xl'], overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: shadowVars['--cl-shadow-card'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-lg'], }, }); @@ -52,14 +52,14 @@ export const header = stylex.create({ rowGap: space['1'], }, title: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-base-size'], fontWeight: fontWeightVars['--cl-font-semibold'], lineHeight: typeScaleVars['--cl-text-base-leading'], textWrap: 'balance', }, description: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-sm-size'], lineHeight: typeScaleVars['--cl-text-sm-leading'], textWrap: 'pretty', diff --git a/packages/ui/src/mosaic/components/combobox/combobox.styles.ts b/packages/ui/src/mosaic/components/combobox/combobox.styles.ts index 31fda1947b4..64236e7bcbc 100644 --- a/packages/ui/src/mosaic/components/combobox/combobox.styles.ts +++ b/packages/ui/src/mosaic/components/combobox/combobox.styles.ts @@ -1,6 +1,14 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, fontFamilyVars, fontWeightVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; +import { + colorVars, + fontFamilyVars, + fontWeightVars, + radiusVars, + shadowVars, + space, + typeScaleVars, +} from '../../tokens.stylex'; export const styles = stylex.create({ positioner: { @@ -10,11 +18,9 @@ export const styles = stylex.create({ borderRadius: radiusVars['--cl-radius-lg'], outline: 'none', overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent), - 0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-md'], + color: colorVars['--cl-color-foreground'], opacity: { default: 1, ':where([data-ending-style], [data-starting-style])': 0, @@ -84,7 +90,7 @@ export const styles = stylex.create({ empty: { paddingBlock: space['6'], paddingInline: space['3'], - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontFamily: fontFamilyVars['--cl-font-family-sans'], fontSize: typeScaleVars['--cl-text-sm-size'], textAlign: 'center', diff --git a/packages/ui/src/mosaic/components/dialog/dialog.styles.ts b/packages/ui/src/mosaic/components/dialog/dialog.styles.ts index 6ea8473ff4d..10d9ab3d461 100644 --- a/packages/ui/src/mosaic/components/dialog/dialog.styles.ts +++ b/packages/ui/src/mosaic/components/dialog/dialog.styles.ts @@ -1,6 +1,6 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, durationVars, easingVars, radiusVars, space } from '../../tokens.stylex'; +import { colorVars, durationVars, easingVars, radiusVars, shadowVars, space } from '../../tokens.stylex'; // How far the contents of a surface beneath a stacked prompt are veiled toward its own background. // Declared up here rather than beside `STACK_SCALE` further down because `sizes` reads it, and @@ -209,11 +209,11 @@ export const styles = stylex.create({ // Cleared because `FloatingFocusManager` focuses the popup itself when it holds no // tabbable content, which would otherwise draw a ring around the whole surface. outline: 'none', - backgroundColor: colorVars['--cl-color-card'], - // Matches `Menu`. The two schemes are different treatments, not one at two strengths: light - // gets the two drop layers and a dark hairline, dark drops them to `transparent` and separates - // with a light hairline instead — a shadow reads as depth against a light page and as nothing - // against a dark one. + backgroundColor: colorVars['--cl-color-background'], + // The `lg` shadow, shared with `Card`. The two schemes are different + // treatments, not one at two strengths: light gets the two drop layers and a dark hairline, + // dark drops them to `transparent` and separates with a light hairline instead — a shadow + // reads as depth against a light page and as nothing against a dark one. // // Branched per COLOUR via `light-dark()`, which is the only shape available: `light-dark()` // resolves to a colour and cannot carry an offset or a blur, so the geometry has to be shared. @@ -221,10 +221,8 @@ export const styles = stylex.create({ // OS preference while `light-dark()` tracks the `color-scheme` in scope, so an app forcing a // scheme (swingset's own toggle does, via next-themes) would take its colours from one and its // geometry from the other. - boxShadow: `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent), - 0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, - color: colorVars['--cl-color-card-foreground'], + boxShadow: shadowVars['--cl-shadow-lg'], + color: colorVars['--cl-color-foreground'], display: 'flex', flexDirection: 'column', // A dialog holds prose it did not author — an email address, an org slug, an API key — and a @@ -238,7 +236,7 @@ export const styles = stylex.create({ inset: 0, // Follows the popup's own radius. borderRadius: 'inherit', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], content: '""', opacity: 'var(--_cl-stack-veil, 0)', pointerEvents: 'none', diff --git a/packages/ui/src/mosaic/components/dialog/dialog.test.tsx b/packages/ui/src/mosaic/components/dialog/dialog.test.tsx index 5b92bdf8d7b..de6fa5b7286 100644 --- a/packages/ui/src/mosaic/components/dialog/dialog.test.tsx +++ b/packages/ui/src/mosaic/components/dialog/dialog.test.tsx @@ -527,7 +527,7 @@ describe('popup surface', () => { // an atom from its property and value, so a probe with the popup's own values yields the very // atoms `styles.popup` declares. const probe = stylex.create({ - background: { backgroundColor: colorVars['--cl-color-card'] }, + background: { backgroundColor: colorVars['--cl-color-background'] }, radius: { borderRadius: radiusVars['--cl-radius-xl'] }, }); diff --git a/packages/ui/src/mosaic/components/drawer/drawer.styles.ts b/packages/ui/src/mosaic/components/drawer/drawer.styles.ts index 5ad97e9a3b3..2e06abd4dc9 100644 --- a/packages/ui/src/mosaic/components/drawer/drawer.styles.ts +++ b/packages/ui/src/mosaic/components/drawer/drawer.styles.ts @@ -1,6 +1,6 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, durationVars, easingVars, radiusVars, space } from '../../tokens.stylex'; +import { colorVars, durationVars, easingVars, radiusVars, shadowVars, space } from '../../tokens.stylex'; // The dialog's scrim, and the nested value a dialog paints over a `profile` or a `card` — see // `dialog.styles.ts` for the composite arithmetic. A sheet opening from inside a profile dialog is @@ -77,14 +77,14 @@ export const styles = stylex.create({ borderWidth: { default: null, '@media (forced-colors: active)': '1px' }, outline: 'none', overscrollBehavior: 'contain', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], borderStartEndRadius: radiusVars['--cl-radius-2xl'], borderStartStartRadius: radiusVars['--cl-radius-2xl'], // No drop shadow — the sheet sits on the screen edge, so there is nothing for it to float over. // The hairline ring stays: a faint dark edge in light, and the light edge that separates a dark // sheet from a dark page. - boxShadow: `0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, - color: colorVars['--cl-color-card-foreground'], + boxShadow: shadowVars['--cl-shadow-sm'], + color: colorVars['--cl-color-foreground'], display: 'flex', flexDirection: 'column', marginBlockEnd: `calc(-1 * ${BLEED})`, @@ -135,7 +135,7 @@ export const styles = stylex.create({ // is not a press on the handle, and leaves it alone. '--_cl-grip-color': { default: colorVars['--cl-color-border'], - ':active': `color-mix(in oklab, ${colorVars['--cl-color-border']}, ${colorVars['--cl-color-neutral-faded']} 35%)`, + ':active': `color-mix(in oklab, ${colorVars['--cl-color-border']}, ${colorVars['--cl-color-foreground-secondary']} 35%)`, }, placeItems: 'center', display: 'grid', diff --git a/packages/ui/src/mosaic/components/field/field.styles.ts b/packages/ui/src/mosaic/components/field/field.styles.ts index 50b75c9e063..b7f5406c529 100644 --- a/packages/ui/src/mosaic/components/field/field.styles.ts +++ b/packages/ui/src/mosaic/components/field/field.styles.ts @@ -11,14 +11,14 @@ export const styles = stylex.create({ flexDirection: 'column', }, label: { - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], fontWeight: fontWeightVars['--cl-font-medium'], }, message: { margin: 0, }, description: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], }, messageRoot: { overflow: 'clip', diff --git a/packages/ui/src/mosaic/components/icon/icon-frame.styles.ts b/packages/ui/src/mosaic/components/icon/icon-frame.styles.ts index 8a1b94eb7c5..30dd9ac5094 100644 --- a/packages/ui/src/mosaic/components/icon/icon-frame.styles.ts +++ b/packages/ui/src/mosaic/components/icon/icon-frame.styles.ts @@ -2,9 +2,6 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, radiusVars, space } from '../../tokens.stylex'; -// Figma uses the 'page faded' token -const filledBackground = 'light-dark(oklch(0.9702 0 0), oklch(0.2393 0 0))'; - export const styles = stylex.create({ base: { borderColor: 'transparent', @@ -23,7 +20,7 @@ export const styles = stylex.create({ borderColor: colorVars['--cl-color-border'], }, filled: { - backgroundColor: filledBackground, + backgroundColor: colorVars['--cl-color-background-subtle'], }, }); diff --git a/packages/ui/src/mosaic/components/input/input.styles.ts b/packages/ui/src/mosaic/components/input/input.styles.ts index df7776bc2f9..3feda365e7e 100644 --- a/packages/ui/src/mosaic/components/input/input.styles.ts +++ b/packages/ui/src/mosaic/components/input/input.styles.ts @@ -4,7 +4,7 @@ import { colorVars, fontFamilyVars, fontWeightVars, radiusVars, space, typeScale export const styles = stylex.create({ base: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], display: 'block', fontFamily: fontFamilyVars['--cl-font-family-sans'], minWidth: 0, diff --git a/packages/ui/src/mosaic/components/item/item.styles.ts b/packages/ui/src/mosaic/components/item/item.styles.ts index 9efd3a52470..46aaf947b39 100644 --- a/packages/ui/src/mosaic/components/item/item.styles.ts +++ b/packages/ui/src/mosaic/components/item/item.styles.ts @@ -9,11 +9,11 @@ export const item = stylex.create({ // rule, so the value crosses the element boundary as a custom property. It is restated in // `interactive` rather than gaining a hover branch here: StyleX resolves a property to the // last style that declares it, so the two can't merge. - '--_cl-icon-color': colorVars['--cl-color-neutral-faded'], + '--_cl-icon-color': colorVars['--cl-color-foreground-secondary'], borderRadius: radiusVars['--cl-radius-lg'], paddingInline: space['1.5'], alignItems: 'center', - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], display: 'flex', fontFamily: fontFamilyVars['--cl-font-family-sans'], fontSize: typeScaleVars['--cl-text-sm-size'], @@ -31,24 +31,24 @@ export const item = stylex.create({ // instead of the native attribute. interactive: { '--_cl-icon-color': { - default: colorVars['--cl-color-neutral-faded'], + default: colorVars['--cl-color-foreground-secondary'], '@media (hover: hover)': { default: null, - ':hover:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-neutral'], + ':hover:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-foreground'], }, }, backgroundColor: { default: null, - ':active:not(:disabled, [aria-disabled="true"])': `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 8%, transparent)`, + ':active:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-neutral-alpha-200'], '@media (hover: hover)': { - ':hover:not(:disabled, [aria-disabled="true"])': `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`, + ':hover:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-neutral-alpha-100'], }, }, color: { - default: colorVars['--cl-color-neutral-faded'], + default: colorVars['--cl-color-foreground-secondary'], '@media (hover: hover)': { default: null, - ':hover:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-neutral'], + ':hover:not(:disabled, [aria-disabled="true"])': colorVars['--cl-color-foreground'], }, }, cursor: { @@ -110,7 +110,7 @@ export const label = stylex.create({ }, default: { - color: colorVars['--cl-color-neutral'], + color: colorVars['--cl-color-foreground'], }, // Declares no color, so `reset`'s `inherit` stands and the row's own color reaches it. That is // what carries it through the hover promotion on an interactive row, which a fixed color would @@ -121,7 +121,7 @@ export const label = stylex.create({ export const description = stylex.create({ base: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-xs-size'], fontWeight: fontWeightVars['--cl-font-normal'], lineHeight: typeScaleVars['--cl-text-xs-leading'], @@ -158,7 +158,7 @@ export const group = stylex.create({ export const separator = stylex.create({ base: { borderStyle: 'none', - backgroundColor: colorVars['--cl-color-border-faded'], + backgroundColor: colorVars['--cl-color-border-subtle'], flexShrink: 0, height: '1px', width: '100%', diff --git a/packages/ui/src/mosaic/components/menu/menu.styles.ts b/packages/ui/src/mosaic/components/menu/menu.styles.ts index a81e051855d..55a15e1ef15 100644 --- a/packages/ui/src/mosaic/components/menu/menu.styles.ts +++ b/packages/ui/src/mosaic/components/menu/menu.styles.ts @@ -1,6 +1,14 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, fontFamilyVars, fontWeightVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; +import { + colorVars, + fontFamilyVars, + fontWeightVars, + radiusVars, + shadowVars, + space, + typeScaleVars, +} from '../../tokens.stylex'; // Positioning is applied inline by the headless positioner; this only clears the // focus outline it receives. No z-index: the portalled, fixed positioner already @@ -15,11 +23,9 @@ export const popup = stylex.create({ base: { borderRadius: radiusVars['--cl-radius-lg'], outline: 'none', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent), - 0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-md'], + color: colorVars['--cl-color-foreground'], opacity: { default: 1, ':is([data-ending-style])': 0, @@ -73,9 +79,9 @@ export const item = stylex.create({ alignItems: 'center', backgroundColor: { default: 'transparent', - ':is([data-active])': `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`, + ':is([data-active])': colorVars['--cl-color-neutral-alpha-100'], '@media (hover: hover)': { - ':hover': `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`, + ':hover': colorVars['--cl-color-neutral-alpha-100'], }, }, cursor: { default: 'pointer', ':is([data-disabled])': 'not-allowed' }, @@ -109,9 +115,9 @@ export const item = stylex.create({ negative: { backgroundColor: { default: 'transparent', - ':is([data-active])': `color-mix(in oklab, ${colorVars['--cl-color-negative']} 8%, transparent)`, + ':is([data-active])': colorVars['--cl-color-negative-alpha-200'], '@media (hover: hover)': { - ':hover': `color-mix(in oklab, ${colorVars['--cl-color-negative']} 8%, transparent)`, + ':hover': colorVars['--cl-color-negative-alpha-200'], }, }, color: colorVars['--cl-color-negative'], diff --git a/packages/ui/src/mosaic/components/otp/otp.styles.ts b/packages/ui/src/mosaic/components/otp/otp.styles.ts index 7996578d182..42887c5a8e3 100644 --- a/packages/ui/src/mosaic/components/otp/otp.styles.ts +++ b/packages/ui/src/mosaic/components/otp/otp.styles.ts @@ -10,10 +10,7 @@ import { typeScaleVars, } from '../../tokens.stylex'; -const positiveFocusShadow = `0 0 0 3px light-dark( - color-mix(in oklab, ${colorVars['--cl-color-positive']} 12%, transparent), - color-mix(in oklab, ${colorVars['--cl-color-positive']} 15%, transparent) -)`; +const positiveFocusShadow = `0 0 0 3px ${colorVars['--cl-color-positive-alpha-300']}`; export const styles = stylex.create({ root: { @@ -24,8 +21,8 @@ export const styles = stylex.create({ }, slot: { borderRadius: radiusVars['--cl-radius-lg'], - caretColor: colorVars['--cl-color-primary'], - color: colorVars['--cl-color-card-foreground'], + caretColor: colorVars['--cl-color-brand'], + color: colorVars['--cl-color-foreground'], fontFamily: fontFamilyVars['--cl-font-family-sans'], fontSize: typeScaleVars['--cl-text-lg-size'], fontVariantNumeric: 'tabular-nums', diff --git a/packages/ui/src/mosaic/components/phone-input/phone-input.styles.ts b/packages/ui/src/mosaic/components/phone-input/phone-input.styles.ts index a8644fe3623..23a6f199762 100644 --- a/packages/ui/src/mosaic/components/phone-input/phone-input.styles.ts +++ b/packages/ui/src/mosaic/components/phone-input/phone-input.styles.ts @@ -1,6 +1,6 @@ import * as stylex from '@stylexjs/stylex'; -import { colorVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; +import { colorVars, radiusVars, shadowVars, space, typeScaleVars } from '../../tokens.stylex'; export const styles = stylex.create({ triggerContent: { @@ -29,7 +29,7 @@ export const styles = stylex.create({ }, prefix: { gap: space['2'], - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], fontVariantNumeric: 'tabular-nums', paddingInlineEnd: 0, paddingInlineStart: space['2'], @@ -41,11 +41,9 @@ export const styles = stylex.create({ popup: { borderRadius: radiusVars['--cl-radius-lg'], overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent), - 0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-md'], + color: colorVars['--cl-color-foreground'], }, countrySearchContainer: { paddingInline: space['2'], @@ -61,7 +59,7 @@ export const styles = stylex.create({ minWidth: 0, }, optionCode: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], flexShrink: 0, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap', diff --git a/packages/ui/src/mosaic/components/profile/profile.styles.ts b/packages/ui/src/mosaic/components/profile/profile.styles.ts index bd2543ec370..2b8f0e2a71c 100644 --- a/packages/ui/src/mosaic/components/profile/profile.styles.ts +++ b/packages/ui/src/mosaic/components/profile/profile.styles.ts @@ -114,7 +114,7 @@ export const styles = stylex.create({ // `clip` rather than `hidden`: the surface must never become a scroll container itself, or // focusing something in the content column would scroll the whole surface instead of the column. overflow: 'clip', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], blockSize: { [compact]: '100dvh', default: '45rem', @@ -122,7 +122,7 @@ export const styles = stylex.create({ // In a page — standalone or inline — the frame is its border alone, the way a card sits flat in // content. The card's elevation belongs to the overlay; see `layoutInDialog`. boxShadow: 'none', - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], display: 'grid', gridTemplateColumns: { [compact]: 'minmax(0, 1fr)', @@ -159,7 +159,7 @@ export const styles = stylex.create({ // popup is the screen and there is nothing to lift off. boxShadow: { [compact]: 'none', - default: shadowVars['--cl-shadow-card'], + default: shadowVars['--cl-shadow-lg'], }, flexGrow: 1, minHeight: 0, @@ -214,16 +214,16 @@ export const styles = stylex.create({ alignItems: 'center', backgroundColor: { default: 'transparent', - ':where([data-selected])': colorVars['--cl-color-border-faded'], - ':active': colorVars['--cl-color-border-faded'], + ':where([data-selected])': colorVars['--cl-color-border-subtle'], + ':active': colorVars['--cl-color-border-subtle'], '@media (hover: hover)': { default: null, - ':hover:not(:active):not([data-selected])': colorVars['--cl-color-border-faded'], + ':hover:not(:active):not([data-selected])': colorVars['--cl-color-border-subtle'], }, }, color: { - default: colorVars['--cl-color-neutral-faded'], - ':where([data-selected])': colorVars['--cl-color-card-foreground'], + default: colorVars['--cl-color-foreground-secondary'], + ':where([data-selected])': colorVars['--cl-color-foreground'], }, cursor: 'pointer', display: 'flex', @@ -342,7 +342,7 @@ export const styles = stylex.create({ * own variable rather than `color`, which the icon sets itself. */ caret: { - '--_cl-icon-color': colorVars['--cl-color-neutral-faded'], + '--_cl-icon-color': colorVars['--cl-color-foreground-secondary'], fontSize: '0.6em', marginInlineStart: '0.25em', verticalAlign: 'middle', diff --git a/packages/ui/src/mosaic/components/section/section.styles.ts b/packages/ui/src/mosaic/components/section/section.styles.ts index 17fc126453d..a886e8d57fe 100644 --- a/packages/ui/src/mosaic/components/section/section.styles.ts +++ b/packages/ui/src/mosaic/components/section/section.styles.ts @@ -13,7 +13,7 @@ export const styles = stylex.create({ width: '100%', }, title: { - color: colorVars['--cl-color-neutral'], + color: colorVars['--cl-color-foreground'], fontWeight: fontWeightVars['--cl-font-medium'], }, group: { @@ -22,7 +22,7 @@ export const styles = stylex.create({ borderStyle: 'solid', borderWidth: '1px', overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], width: '100%', }, row: { @@ -110,13 +110,13 @@ export const styles = stylex.create({ minWidth: 0, }, label: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-sm-size'], fontWeight: fontWeightVars['--cl-font-medium'], lineHeight: typeScaleVars['--cl-text-sm-leading'], }, description: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-sm-size'], fontWeight: fontWeightVars['--cl-font-normal'], lineHeight: typeScaleVars['--cl-text-sm-leading'], diff --git a/packages/ui/src/mosaic/components/select/select.styles.ts b/packages/ui/src/mosaic/components/select/select.styles.ts index 9f510a4425d..6207989a7a4 100644 --- a/packages/ui/src/mosaic/components/select/select.styles.ts +++ b/packages/ui/src/mosaic/components/select/select.styles.ts @@ -7,6 +7,7 @@ import { fontFamilyVars, fontWeightVars, radiusVars, + shadowVars, space, typeScaleVars, } from '../../tokens.stylex'; @@ -34,15 +35,14 @@ export const positioner = stylex.create({ export const popup = stylex.create({ base: { // A real border, not a shadow ring, so it occupies the 1px of layout the alignment counts on. - borderColor: 'light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))', + borderColor: `light-dark(color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent), color-mix(in oklab, ${colorVars['--cl-color-neutral']} 10%, transparent))`, borderRadius: radiusVars['--cl-radius-lg'], borderStyle: 'solid', borderWidth: '1px', outline: 'none', - backgroundColor: colorVars['--cl-color-card'], - boxShadow: `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent)`, - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + boxShadow: shadowVars['--cl-shadow-md'], + color: colorVars['--cl-color-foreground'], opacity: { default: 1, ':where([data-starting-style], [data-ending-style])': 0, @@ -101,7 +101,7 @@ export const option = stylex.create({ ':hover': `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`, }, }, - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], cursor: { default: 'pointer', ':where([data-disabled])': 'not-allowed' }, display: 'flex', // The viewport is a height-capped flex column; without this the rows squash instead of scrolling. @@ -140,7 +140,7 @@ export const content = stylex.create({ export const description = stylex.create({ base: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontWeight: fontWeightVars['--cl-font-normal'], }, }); @@ -148,7 +148,7 @@ export const description = stylex.create({ // Rendered on every row and shown only on the selected one, so labels never shift with selection. export const indicator = stylex.create({ base: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], visibility: { default: 'hidden', [stylex.when.ancestor('[data-selected]', selectOptionScope)]: 'visible', diff --git a/packages/ui/src/mosaic/features/user-button/user-button.styles.ts b/packages/ui/src/mosaic/features/user-button/user-button.styles.ts index 014eac50007..b159bb9c13f 100644 --- a/packages/ui/src/mosaic/features/user-button/user-button.styles.ts +++ b/packages/ui/src/mosaic/features/user-button/user-button.styles.ts @@ -48,7 +48,7 @@ export const styles = stylex.create({ // Matches `Item.Label`, so the trigger names a workspace the same way its row does. Capped, // because the trigger sits in a host app's chrome and a long workspace name would push it apart. triggerName: { - color: colorVars['--cl-color-neutral'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-sm-size'], fontWeight: fontWeightVars['--cl-font-medium'], lineHeight: typeScaleVars['--cl-text-sm-leading'], @@ -56,7 +56,7 @@ export const styles = stylex.create({ }, triggerCaret: { - '--_cl-icon-color': colorVars['--cl-color-neutral-faded'], + '--_cl-icon-color': colorVars['--cl-color-foreground-secondary'], marginInlineEnd: space['1'], }, diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-account-section/user-profile-verify-email-link.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-account-section/user-profile-verify-email-link.styles.ts index 267f95db704..93cc5c02aef 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-account-section/user-profile-verify-email-link.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-account-section/user-profile-verify-email-link.styles.ts @@ -21,6 +21,6 @@ export const styles = stylex.create({ fontWeight: fontWeightVars['--cl-font-medium'], }, emailAddress: { - color: colorVars['--cl-color-primary'], + color: colorVars['--cl-color-brand'], }, }); diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-api-keys-panel.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-api-keys-panel.styles.ts index 41d84929bc7..c2439445187 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-api-keys-panel.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-api-keys-panel.styles.ts @@ -10,13 +10,13 @@ export const styles = stylex.create({ cell: { paddingBlock: space['3'], paddingInline: space['4'], - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-sm-size'], lineHeight: typeScaleVars['--cl-text-sm-leading'], verticalAlign: 'middle', }, checkbox: { - accentColor: colorVars['--cl-color-primary'], + accentColor: colorVars['--cl-color-brand'], cursor: 'pointer', height: space['4'], width: space['4'], @@ -29,25 +29,25 @@ export const styles = stylex.create({ }, emptyCell: { paddingBlock: space['8'], - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-sm-size'], lineHeight: typeScaleVars['--cl-text-sm-leading'], textAlign: 'center', }, header: { - backgroundColor: colorVars['--cl-color-border-faded'], + backgroundColor: colorVars['--cl-color-border-subtle'], }, headerCell: { paddingBlock: space['2.5'], paddingInline: space['4'], - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-xs-size'], fontWeight: fontWeightVars['--cl-font-medium'], lineHeight: typeScaleVars['--cl-text-xs-leading'], textAlign: 'start', }, keyDescription: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], marginBlockStart: space['0.5'], @@ -55,7 +55,7 @@ export const styles = stylex.create({ keyName: { gap: space['2'], alignItems: 'center', - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], display: 'flex', fontSize: typeScaleVars['--cl-text-sm-size'], fontWeight: fontWeightVars['--cl-font-medium'], @@ -67,7 +67,7 @@ export const styles = stylex.create({ pageSizeLabel: { gap: space['2'], alignItems: 'center', - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], display: 'flex', fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], @@ -79,8 +79,8 @@ export const styles = stylex.create({ borderWidth: '1px', paddingBlock: space['1'], paddingInline: space['2'], - backgroundColor: colorVars['--cl-color-card'], - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], }, @@ -111,7 +111,7 @@ export const styles = stylex.create({ paddingInlineStart: space['8'], }, searchIcon: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], insetInlineStart: space['3'], pointerEvents: 'none', position: 'absolute', @@ -137,7 +137,7 @@ export const styles = stylex.create({ borderStyle: 'solid', borderWidth: '1px', overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], width: '100%', }, toolbar: { diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-billing-history-section.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-billing-history-section.styles.ts index 6aca6ea3d26..6e0c02675ac 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-billing-history-section.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-billing-history-section.styles.ts @@ -7,7 +7,7 @@ export const styles = stylex.create({ textAlign: 'end', }, amountCell: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-sm-size'], lineHeight: typeScaleVars['--cl-text-sm-leading'], }, @@ -18,18 +18,18 @@ export const styles = stylex.create({ }, emptyCell: { paddingBlock: space['6'], - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-sm-size'], lineHeight: typeScaleVars['--cl-text-sm-leading'], textAlign: 'center', }, header: { - backgroundColor: colorVars['--cl-color-border-faded'], + backgroundColor: colorVars['--cl-color-border-subtle'], }, headerCell: { paddingBlock: space['2.5'], paddingInline: space['4'], - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-xs-size'], fontWeight: fontWeightVars['--cl-font-medium'], lineHeight: typeScaleVars['--cl-text-xs-leading'], @@ -40,7 +40,7 @@ export const styles = stylex.create({ }, invoiceId: { overflow: 'hidden', - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], marginBlockStart: space['0.5'], @@ -48,7 +48,7 @@ export const styles = stylex.create({ whiteSpace: 'nowrap', }, invoiceLabel: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-sm-size'], fontWeight: fontWeightVars['--cl-font-medium'], lineHeight: typeScaleVars['--cl-text-sm-leading'], @@ -56,7 +56,7 @@ export const styles = stylex.create({ pageSizeLabel: { gap: space['2'], alignItems: 'center', - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], display: 'flex', fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], @@ -68,8 +68,8 @@ export const styles = stylex.create({ borderWidth: '1px', paddingBlock: space['1'], paddingInline: space['2'], - backgroundColor: colorVars['--cl-color-card'], - color: colorVars['--cl-color-card-foreground'], + backgroundColor: colorVars['--cl-color-background'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-xs-size'], lineHeight: typeScaleVars['--cl-text-xs-leading'], }, @@ -100,7 +100,7 @@ export const styles = stylex.create({ borderStyle: 'solid', borderWidth: '1px', overflow: 'hidden', - backgroundColor: colorVars['--cl-color-card'], + backgroundColor: colorVars['--cl-color-background'], width: '100%', }, statusColumn: { diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-billing-panel.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-billing-panel.styles.ts index 2ba3f8e403c..639aa008564 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-billing-panel.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-billing-panel.styles.ts @@ -4,7 +4,7 @@ import { colorVars, fontWeightVars, space, typeScaleVars } from '../../tokens.st export const styles = stylex.create({ amount: { - color: colorVars['--cl-color-card-foreground'], + color: colorVars['--cl-color-foreground'], fontSize: typeScaleVars['--cl-text-base-size'], fontWeight: fontWeightVars['--cl-font-semibold'], lineHeight: typeScaleVars['--cl-text-base-leading'], diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-password-section/user-profile-password-section.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-password-section/user-profile-password-section.styles.ts index dbc4e473700..74b0946ba9d 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-password-section/user-profile-password-section.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-password-section/user-profile-password-section.styles.ts @@ -9,7 +9,7 @@ export const styles = stylex.create({ display: 'flex', }, checkbox: { - accentColor: colorVars['--cl-color-primary'], + accentColor: colorVars['--cl-color-brand'], cursor: 'pointer', flexShrink: 0, marginBlockStart: '2px', @@ -26,7 +26,7 @@ export const styles = stylex.create({ fontWeight: fontWeightVars['--cl-font-medium'], }, checkboxDescription: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], }, managedBy: { gap: space['1.5'], @@ -39,6 +39,6 @@ export const styles = stylex.create({ width: space['4'], }, managedByText: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], }, }); diff --git a/packages/ui/src/mosaic/features/user-profile/user-profile-security-panel.styles.ts b/packages/ui/src/mosaic/features/user-profile/user-profile-security-panel.styles.ts index 9c7736e3be9..e2e2881ce5f 100644 --- a/packages/ui/src/mosaic/features/user-profile/user-profile-security-panel.styles.ts +++ b/packages/ui/src/mosaic/features/user-profile/user-profile-security-panel.styles.ts @@ -12,17 +12,17 @@ export const styles = stylex.create({ flexWrap: 'wrap', }, icon: { - color: colorVars['--cl-color-neutral-faded'], + color: colorVars['--cl-color-foreground-secondary'], display: 'block', height: space['4.5'], width: space['4.5'], }, media: { - borderColor: colorVars['--cl-color-border-faded'], + borderColor: colorVars['--cl-color-border-subtle'], borderRadius: radiusVars['--cl-radius-md'], borderStyle: 'solid', borderWidth: '1px', - backgroundColor: colorVars['--cl-color-background'], + backgroundColor: colorVars['--cl-color-background-subtle'], }, root: { gap: space['4'], diff --git a/packages/ui/src/mosaic/props.ts b/packages/ui/src/mosaic/props.ts index 858ea73f62b..ed163be9224 100644 --- a/packages/ui/src/mosaic/props.ts +++ b/packages/ui/src/mosaic/props.ts @@ -50,7 +50,7 @@ export type MosaicComponentProps MosaicStyleProps; // The public styling contract, emitted onto a component's root element: -// 1. `--cl-*` vars — from `tokens.stylex.ts` (`:root { --cl-color-primary: … }`) +// 1. `--cl-*` vars — from `tokens.stylex.ts` (`:root { --cl-color-brand: … }`) // 2. `.cl-` class — from `themeProps` (`.cl-button { … }`) // 3. `data-` attrs — from `themeProps` (`.cl-button[data-variant='outline']`) // diff --git a/packages/ui/src/mosaic/tokens.stylex.ts b/packages/ui/src/mosaic/tokens.stylex.ts index d30e5f57f79..9c7cf3bfe40 100644 --- a/packages/ui/src/mosaic/tokens.stylex.ts +++ b/packages/ui/src/mosaic/tokens.stylex.ts @@ -7,9 +7,9 @@ import * as stylex from '@stylexjs/stylex'; // these become real, stable custom properties a consumer can override in plain // CSS without touching StyleX: // -// :root { --cl-color-primary: rebeccapurple; } +// :root { --cl-color-brand: rebeccapurple; } // -// StyleX still emits `:root { --cl-color-primary: … }` as the default. Following +// StyleX still emits `:root { --cl-color-brand: … }` as the default. Following // astryx's structure: each group is a plain `*Defaults` object wrapped by // `defineVars`. The `*VarName` unions are derived from the exported `*Vars` in // `styles/index.ts` — the `@stylexjs/enforce-extension` rule requires a @@ -23,39 +23,63 @@ import * as stylex from '@stylexjs/stylex'; // Color Tokens // ============================================================================= -const colorDefaults = { - '--cl-color-primary': 'light-dark(oklch(0.205 0 0), oklch(0.922 0 0))', - '--cl-color-primary-foreground': 'light-dark(oklch(0.985 0 0), oklch(0.205 0 0))', - '--cl-color-primary-faded': 'light-dark(oklch(0.9583 0.0214 291.74), oklch(0.3097 0.1008 285.05))', - - '--cl-color-neutral': 'light-dark(oklch(0.2928 0.0163 285.35), oklch(0.9854 0.0013 286.38))', - '--cl-color-neutral-foreground': 'light-dark(oklch(0.24 0 0), oklch(0.96 0 0))', - '--cl-color-neutral-faded': 'light-dark(oklch(0.5697 0.0246 279.94), oklch(0.6953 0.0261 285.7))', - - '--cl-color-negative': 'light-dark(oklch(0.577 0.245 27.325), oklch(0.637 0.237 25.331))', - '--cl-color-negative-foreground': 'oklch(0.985 0 0)', - '--cl-color-negative-faded': 'light-dark(oklch(0.9757 0.0118 17.36), oklch(0.255 0.0604 22.31))', - - '--cl-color-positive': 'light-dark(oklch(0.548 0.153 152.535), oklch(0.696 0.17 162.48))', - '--cl-color-positive-foreground': 'oklch(0.985 0 0)', - '--cl-color-positive-faded': 'light-dark(oklch(0.9859 0.0164 156.92), oklch(0.3297 0.052 152.31))', - - '--cl-color-warning': 'light-dark(oklch(0.646 0.222 41.116), oklch(0.75 0.183 55.934))', - '--cl-color-warning-foreground': 'oklch(0.985 0 0)', - '--cl-color-warning-faded': 'light-dark(oklch(0.9799 0.0147 70.89), oklch(0.2725 0.0547 55.7))', - - '--cl-color-background': 'light-dark(oklch(0.985 0 0), oklch(0.2686 0 0))', - - '--cl-color-card': 'light-dark(oklch(1 0 0), oklch(0.205 0 0))', - '--cl-color-card-foreground': 'light-dark(oklch(0.145 0 0), oklch(0.985 0 0))', - - '--cl-color-input': 'light-dark(oklch(1 0 0), oklch(0.2046 0 0))', - '--cl-color-input-placeholder': '#a3a3a3', +// Every achromatic token is `--cl-color-neutral` mixed into `--cl-color-background`, so +// retinting neutral (a warm gray, say) retints the whole ramp. The percentages reproduce +// the Figma gray scale: grays have zero chroma, so an oklab mix is linear in lightness. +// Dark mode needs its own percentage because its background is a 900, not black. +const neutralMix = (light: number, dark: number) => + `light-dark(color-mix(in oklab, var(--cl-color-neutral) ${light}%, var(--cl-color-background)), color-mix(in oklab, var(--cl-color-neutral) ${dark}%, var(--cl-color-background)))`; - '--cl-color-border': 'light-dark(oklch(0.9219 0 0), oklch(0.3715 0 0))', - '--cl-color-border-faded': 'light-dark(oklch(0.9702 0 0), oklch(0.296 0.0126 285.61))', - - '--cl-color-ring': 'light-dark(oklch(0.205 0 0), oklch(0.922 0 0))', +const colorDefaults = { + '--cl-color-neutral': 'light-dark(oklch(0 0 0), oklch(1 0 0))', + '--cl-color-neutral-alpha-100': 'color-mix(in oklab, var(--cl-color-neutral) 6%, transparent)', + '--cl-color-neutral-alpha-200': 'color-mix(in oklab, var(--cl-color-neutral) 8%, transparent)', + '--cl-color-neutral-alpha-300': 'color-mix(in oklab, var(--cl-color-neutral) 12%, transparent)', + + '--cl-color-background': 'light-dark(oklch(1 0 0), oklch(0.2046 0 0))', + '--cl-color-background-subtle': neutralMix(3, 4), + + '--cl-color-foreground': neutralMix(79.5, 98), + '--cl-color-foreground-secondary': neutralMix(56, 64.25), + '--cl-color-foreground-disabled': neutralMix(28.5, 44), + + '--cl-color-border': neutralMix(5.5, 8), + '--cl-color-border-subtle': neutralMix(3, 4.25), + + '--cl-color-brand': 'light-dark(oklch(0.2046 0 0), oklch(0.9851 0 0))', + '--cl-color-brand-secondary': 'light-dark(oklch(0.5555 0 0), oklch(0.7155 0 0))', + '--cl-color-brand-border': 'light-dark(oklch(0.9219 0 0), oklch(0.3715 0 0))', + '--cl-color-brand-foreground': 'light-dark(oklch(1 0 0), oklch(0.2046 0 0))', + + '--cl-color-negative': 'light-dark(oklch(0.5903 0.213 26.78), oklch(0.7106 0.1661 22.22))', + '--cl-color-negative-foreground': 'oklch(1 0 0)', + '--cl-color-negative-subtle': 'light-dark(oklch(0.9757 0.0118 17.36), oklch(0.255 0.0604 22.31))', + '--cl-color-negative-border': 'light-dark(oklch(0.8155 0.0983 19.41), oklch(0.3958 0.1331 25.72))', + '--cl-color-negative-alpha-100': 'color-mix(in oklab, var(--cl-color-negative) 6%, transparent)', + '--cl-color-negative-alpha-200': 'color-mix(in oklab, var(--cl-color-negative) 8%, transparent)', + '--cl-color-negative-alpha-300': 'color-mix(in oklab, var(--cl-color-negative) 12%, transparent)', + + '--cl-color-positive': 'light-dark(oklch(0.6082 0.1799 145.47), oklch(0.7227 0.192 149.58))', + '--cl-color-positive-foreground': 'oklch(1 0 0)', + '--cl-color-positive-subtle': 'light-dark(oklch(0.9859 0.0164 156.92), oklch(0.3297 0.052 152.31))', + '--cl-color-positive-border': 'light-dark(oklch(0.7922 0.1959 148.18), oklch(0.4479 0.1083 151.33))', + '--cl-color-positive-alpha-100': 'color-mix(in oklab, var(--cl-color-positive) 6%, transparent)', + '--cl-color-positive-alpha-200': 'color-mix(in oklab, var(--cl-color-positive) 8%, transparent)', + '--cl-color-positive-alpha-300': 'color-mix(in oklab, var(--cl-color-positive) 12%, transparent)', + + '--cl-color-warning': 'light-dark(oklch(0.7099 0.1888 44.94), oklch(0.7576 0.159 55.93))', + '--cl-color-warning-foreground': 'oklch(1 0 0)', + '--cl-color-warning-subtle': 'light-dark(oklch(0.9799 0.0147 70.89), oklch(0.2725 0.0547 55.7))', + '--cl-color-warning-border': 'light-dark(oklch(0.8672 0.0902 63.47), oklch(0.4084 0.1165 38.17))', + '--cl-color-warning-alpha-100': 'color-mix(in oklab, var(--cl-color-warning) 6%, transparent)', + '--cl-color-warning-alpha-200': 'color-mix(in oklab, var(--cl-color-warning) 8%, transparent)', + '--cl-color-warning-alpha-300': 'color-mix(in oklab, var(--cl-color-warning) 12%, transparent)', + + '--cl-color-input': + 'light-dark(var(--cl-color-background), color-mix(in oklab, var(--cl-color-neutral) 4%, var(--cl-color-background)))', + '--cl-color-input-placeholder': neutralMix(28, 64), + + '--cl-color-ring': neutralMix(80, 90), } as const; export const colorVars = stylex.defineVars(colorDefaults); @@ -123,9 +147,9 @@ export const targetVars = stylex.defineVars(targetDefaults); // pointer is elsewhere, the base once it reaches the region, then `hover` and `active` for the // thumb's own two. Each derives from `--cl-scrollbar-thumb` rather than baking its value in, so // they resolve at use time — overriding the base re-derives all three, while any one stays -// individually overridable. The base is itself mixed most of the way toward `--cl-color-card`, +// individually overridable. The base is itself mixed most of the way toward `--cl-color-background`, // which keeps a 4px bar reading as a hairline rather than a hard rule; `idle` carries on in that -// direction, and the other two step back toward `--cl-color-card-foreground`, deepening in light +// direction, and the other two step back toward `--cl-color-foreground`, deepening in light // mode and lightening in dark, since that token already carries both. // // Only the idle → base step can animate. It is set on the scroller, which owns the transition; @@ -146,10 +170,10 @@ const scrollbarThumb = 'var(--cl-scrollbar-thumb)'; const scrollbarDefaults = { '--cl-scrollbar-width': '8px', '--cl-scrollbar-thumb-inset': '2px', - '--cl-scrollbar-thumb': `color-mix(in oklab, ${colorVars['--cl-color-neutral-faded']}, ${colorVars['--cl-color-card']} 55%)`, - '--cl-scrollbar-thumb-idle': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-card']} 45%)`, - '--cl-scrollbar-thumb-hover': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-card-foreground']} 15%)`, - '--cl-scrollbar-thumb-active': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-card-foreground']} 30%)`, + '--cl-scrollbar-thumb': `color-mix(in oklab, ${colorVars['--cl-color-foreground-secondary']}, ${colorVars['--cl-color-background']} 55%)`, + '--cl-scrollbar-thumb-idle': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-background']} 45%)`, + '--cl-scrollbar-thumb-hover': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-foreground']} 15%)`, + '--cl-scrollbar-thumb-active': `color-mix(in oklab, ${scrollbarThumb}, ${colorVars['--cl-color-foreground']} 30%)`, } as const; export const scrollbarVars = stylex.defineVars(scrollbarDefaults); @@ -179,8 +203,6 @@ export const scrollFadeVars = stylex.defineVars(scrollFadeDefaults); // ============================================================================= // Spacing Tokens // ============================================================================= -// `--cl-spacing` is the ONLY exposed custom property (the base unit, Tailwind's -// model). Overriding it rescales every gap, pad, and control height at once. const spacingDefaults = { '--cl-spacing': '0.25rem', @@ -188,12 +210,6 @@ const spacingDefaults = { export const spacingVars = stylex.defineVars(spacingDefaults); -// The scale is `defineVars` (like astryx's `spacingVars`): each step is a StyleX -// var whose default is `calc(var(--cl-spacing) * n)`, so overriding `--cl-spacing` -// still rescales the whole scale. `defineConsts` was tried here but emits no CSS -// across module boundaries — consumers got dangling `var(--hash)` refs. StyleX -// hashes these var names (they aren't `--cl-*`), so only `--cl-spacing` stays a -// stable, targetable custom property. `space['2']` reads like Tailwind's `space-2`. const step = (multiple: number): string => `calc(var(--cl-spacing) * ${multiple})`; export const space = stylex.defineVars({ @@ -283,8 +299,6 @@ export const space = stylex.defineVars({ // ============================================================================= // Typography Tokens — type scale // ============================================================================= -// One named step scale every text-bearing component sizes against. `defineVars` -// values are single CSS values, so size and leading are each their own var. const typeScaleDefaults = { '--cl-text-xs-size': '0.75rem', @@ -316,7 +330,6 @@ export const fontFamilyVars = stylex.defineVars(fontFamilyDefaults); // ============================================================================= // Typography Tokens — font weight // ============================================================================= -// Kept separate from the step scale: weight and size vary independently. const fontWeightDefaults = { '--cl-font-normal': '400', @@ -398,13 +411,6 @@ export const easingVars = stylex.defineVars(easingDefaults); // ============================================================================= // Focus Tokens // ============================================================================= -// The keyboard focus ring, as one definition for the whole system. Components read -// these through `utils/focus-outline.styles.ts`; the `:focus-visible` condition stays -// there, so a theme can restyle the ring but cannot show it to pointer users. -// -// The colour is NOT restated here: it is `--cl-color-ring`, which lives with the other -// colours. A `--cl-focus-outline-color` alias would be a second public name for the same -// value, and the two would drift the moment a consumer overrode one of them. const focusDefaults = { '--cl-focus-outline-width': '2px', @@ -414,14 +420,19 @@ const focusDefaults = { export const focusVars = stylex.defineVars(focusDefaults); -// Elevation. The one card shadow, as a token so every surface at that elevation reads the same: -// two drop layers that fall away in dark, and a hairline ring that is dark on light and light on -// dark. Branched per colour via `light-dark()` since a shadow's geometry cannot branch — see the -// note on `Dialog`'s popup for why `@media (prefers-color-scheme)` is not the escape hatch. +// ============================================================================= +// Shadow Tokens +// ============================================================================= + +const light = (wash: string) => `light-dark(${wash}, transparent)`; +const neutral4 = `color-mix(in oklab, ${colorVars['--cl-color-neutral']} 4%, transparent)`; +const neutral8 = colorVars['--cl-color-neutral-alpha-200']; +const darkRing = `0 0 0 1px light-dark(transparent, color-mix(in oklab, ${colorVars['--cl-color-neutral']} 10%, transparent))`; + const shadowDefaults = { - '--cl-shadow-card': `0 12px 12px -7px light-dark(oklch(0.2046 0 0 / 12%), transparent), - 0 24px 24px -10px light-dark(oklch(0.2046 0 0 / 4%), transparent), - 0 0 0 1px light-dark(oklch(0.2046 0 0 / 4%), oklch(1 0 0 / 10%))`, + '--cl-shadow-sm': `0 1px 2px 0 ${light(neutral8)}, ${darkRing}`, + '--cl-shadow-md': `0 2px 1px -1px ${light(neutral4)}, 0 4px 8px 0 ${light(neutral8)}, 0 0 3px 0 ${light(neutral8)}, ${darkRing}`, + '--cl-shadow-lg': `0 2px 2px -1px ${light(neutral4)}, 0 8px 16px 0 ${light(neutral8)}, 0 0 1px 0 ${light(neutral8)}, ${darkRing}`, }; export const shadowVars = stylex.defineVars(shadowDefaults); diff --git a/packages/ui/src/mosaic/utils/focus-outline.styles.ts b/packages/ui/src/mosaic/utils/focus-outline.styles.ts index ffee40d29f0..ededf6b4ecb 100644 --- a/packages/ui/src/mosaic/utils/focus-outline.styles.ts +++ b/packages/ui/src/mosaic/utils/focus-outline.styles.ts @@ -3,7 +3,7 @@ import * as stylex from '@stylexjs/stylex'; import { colorVars, focusVars } from '../tokens.stylex'; // The keyboard focus ring, in one place. Every focusable Mosaic surface drew the same -// `2px solid --cl-color-primary` inline, so the ring was a dozen copies of one decision +// `2px solid --cl-color-brand` inline, so the ring was a dozen copies of one decision // with nothing keeping them in step — the offsets had already drifted. // // Values come from tokens (`--cl-focus-outline-*` and `--cl-color-ring`), which is how a diff --git a/packages/ui/src/mosaic/utils/input.styles.ts b/packages/ui/src/mosaic/utils/input.styles.ts index 852998c34ff..39d27821218 100644 --- a/packages/ui/src/mosaic/utils/input.styles.ts +++ b/packages/ui/src/mosaic/utils/input.styles.ts @@ -6,11 +6,8 @@ import { colorVars, durationVars, easingVars } from '../tokens.stylex'; // system focus ring, so a text-entry field reads as a field rather than a control. const hoverBorderColor = 'light-dark(#bebebe, #525252)'; const focusShadow = '0 0 0 3px light-dark(rgb(23 23 23 / 8%), rgb(255 255 255 / 8%))'; -const invalidFocusShadow = `0 0 0 3px light-dark( - color-mix(in oklab, ${colorVars['--cl-color-negative']} 12%, transparent), - color-mix(in oklab, ${colorVars['--cl-color-negative']} 15%, transparent) -)`; -const disabledBackgroundColor = `color-mix(in oklab, ${colorVars['--cl-color-primary']} 5%, transparent)`; +const invalidFocusShadow = `0 0 0 3px ${colorVars['--cl-color-negative-alpha-300']}`; +const disabledBackgroundColor = `color-mix(in oklab, ${colorVars['--cl-color-brand']} 5%, transparent)`; export const inputStyles = stylex.create({ base: { diff --git a/packages/ui/src/mosaic/utils/typography.styles.ts b/packages/ui/src/mosaic/utils/typography.styles.ts index e4731eb19d7..4077e7bb1ac 100644 --- a/packages/ui/src/mosaic/utils/typography.styles.ts +++ b/packages/ui/src/mosaic/utils/typography.styles.ts @@ -41,10 +41,10 @@ export const sizes = stylex.create({ // Text has no fill to sit on, so each color resolves to the readable token of its // pair: the saturated one for warning/negative/positive, `-foreground` for neutral -// (`--cl-color-neutral` is a surface fill). +// (`--cl-color-foreground` is a surface fill). export const colors = stylex.create({ - primary: { color: colorVars['--cl-color-primary'] }, - neutral: { color: colorVars['--cl-color-neutral-foreground'] }, + primary: { color: colorVars['--cl-color-brand'] }, + neutral: { color: colorVars['--cl-color-foreground'] }, warning: { color: colorVars['--cl-color-warning'] }, negative: { color: colorVars['--cl-color-negative'] }, positive: { color: colorVars['--cl-color-positive'] }, diff --git a/references/mosaic-architecture.md b/references/mosaic-architecture.md index fc29cb20b60..88a3f7df343 100644 --- a/references/mosaic-architecture.md +++ b/references/mosaic-architecture.md @@ -12,19 +12,21 @@ Once migration is complete, the old system is removed and Mosaic becomes the sol ## Token architecture -Tokens are CSS custom properties declared through `stylex.defineVars` in `tokens.stylex.ts`. Each var is named explicitly (`'--cl-color-primary'`, not a generated hash) so it is a stable, documented handle a consumer can override: +Tokens are CSS custom properties declared through `stylex.defineVars` in `tokens.stylex.ts`. Each var is named explicitly (`'--cl-color-brand'`, not a generated hash) so it is a stable, documented handle a consumer can override: ```ts // packages/ui/src/mosaic/tokens.stylex.ts export const colorVars = stylex.defineVars({ - '--cl-color-primary': 'light-dark(oklch(0.205 0 0), oklch(0.922 0 0))', - '--cl-color-primary-foreground': 'light-dark(oklch(0.985 0 0), oklch(0.205 0 0))', + '--cl-color-brand': 'light-dark(oklch(0.2046 0 0), oklch(0.9851 0 0))', + '--cl-color-brand-foreground': 'light-dark(oklch(1 0 0), oklch(0.2046 0 0))', // … }); ``` Groups: `colorVars`, `radiusVars`, `targetVars`, `scrollbarVars`, `scrollFadeVars`, `spacingVars`, `space`, `typeScaleVars`, `fontFamilyVars`, `fontWeightVars`, `durationVars`, `easingVars`, `focusVars`. +There is no gray scale. Every achromatic token (`--cl-color-foreground`, `-border`, `-background-subtle`, …) is `--cl-color-neutral` (black on light, white on dark) mixed into `--cl-color-background` at a fixed percentage per mode, so overriding neutral to a warm gray retints the whole ramp; in dark mode override `--cl-color-background` too, since the ramp mixes toward it. The `--cl--alpha-100|200|300` tokens in `colorVars` are washes (6, 8, and 12 percent of `--cl-color-neutral`, `-negative`, `-positive`, and `-warning` over `transparent`); overriding the solid color retints its washes. + Light and dark come from CSS `light-dark()` on the default values, so there is no theme object and no re-render on theme change — the browser resolves it. `@stylexjs/enforce-extension` requires a `.stylex.ts` file to export nothing but its `defineVars` results, so the derived token-name unions (`ColorVarName`, `RadiusVarName`, …) live in `styles/index.ts` as `keyof typeof colorVars`. @@ -49,7 +51,7 @@ A theme styles a part from CSS, against that class and its attributes: border-radius: 4px; } .cl-item[data-interactive] { - background-color: var(--cl-color-card); + background-color: var(--cl-color-background); } ``` @@ -94,9 +96,9 @@ const styles = stylex.create({ // `color` and `variant` are independent props, but every pair resolves to one style, so they are // keyed compositely rather than merged at render time. const variants = stylex.create({ - 'filled-primary': { backgroundColor: colorVars['--cl-color-primary'], borderColor: 'transparent' }, + 'filled-primary': { backgroundColor: colorVars['--cl-color-brand'], borderColor: 'transparent' }, 'filled-negative': { backgroundColor: colorVars['--cl-color-negative'], borderColor: 'transparent' }, - 'outline-primary': { backgroundColor: 'transparent', borderColor: colorVars['--cl-color-primary'] }, + 'outline-primary': { backgroundColor: 'transparent', borderColor: colorVars['--cl-color-brand'] }, 'outline-negative': { backgroundColor: 'transparent', borderColor: colorVars['--cl-color-negative'] }, }); @@ -440,7 +442,7 @@ To migrate a component from the old system to Mosaic: cursor: { default: 'pointer', ':is([data-disabled])': 'not-allowed' }, ``` -4. Update token references — e.g. `theme.colors.$primary500` → `colorVars['--cl-color-primary']`. +4. Update token references — e.g. `theme.colors.$primary500` → `colorVars['--cl-color-brand']`. 5. Export the component from `styles/index.ts` and run `pnpm build:mosaic`. The steps above cover the **styling** migration. For **flow** components — where the legacy component also fuses data-fetching and interaction state into the same file — splitting that into the model/controller/view layers and verifying no implicit behavior is dropped is its own end-to-end workflow. See the `mosaic` Claude Code skill (`.claude/skills/mosaic/`), in particular its `references/migration.md`.