Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions packages/tokens/config/tokens.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,15 +518,80 @@ export const config: TokensConfig = {
kind: 'direct',
ref: { family: 'neutral', id: '2' },
},
// Mode-flipping background (SPEC §5.1). Light mode → dark, Dark mode →
// light. Achieved by referencing the opposite end of the neutral spine:
// neutral.12 in light = dark; pivot-mirror makes neutral.12 in dark = light.
inverted: {
kind: 'direct',
ref: { family: 'neutral', id: '12' },
},
// Nested-card hierarchy (Apple Settings.app pattern, SPEC §10.D7).
// Used inside a Grouped section where the outer surface is `primary`
// and inner cards alternate between Grouped.Primary and Grouped.Secondary.
// Tier values match Figma fixture per-mode (light: Gray.25/Gray.0/Gray.25;
// dark: Gray.25/Gray.50≈neutral.1+1/Gray.75≈neutral.2+1).
grouped: {
primary: {
kind: 'direct',
ref: { family: 'neutral', id: '1' },
},
secondary: {
kind: 'direct',
ref: { family: 'neutral', id: '0' },
},
tertiary: {
kind: 'direct',
ref: { family: 'neutral', id: '2' },
},
},
},
// Legacy alias — matches the historical single-tier emission used by
// existing v0.2.x consumers. Effectively `overlay_tiers.base` at the
// legacy stop (40% dark scrim). Retained until next major bump.
overlay: {
kind: 'direct',
ref: { family: 'static', id: 'dark', opacity_stop: 40 },
},
// Full SPEC §5.1 overlay tier set — frosted-overlay scrims (Light
// sub-collection alpha-on-white). Use these for Modal scrims, Tooltip
// backgrounds, and Sheet overlays. Stops are clamped to the production
// opacity ladder; `base` sits at 48 (closest available to the 50% target).
overlay_tiers: {
ghost: {
kind: 'direct',
ref: { family: 'static', id: 'white', opacity_stop: 1 },
},
soft: {
kind: 'direct',
ref: { family: 'static', id: 'white', opacity_stop: 20 },
},
base: {
kind: 'direct',
ref: { family: 'static', id: 'white', opacity_stop: 48 },
},
strong: {
kind: 'direct',
ref: { family: 'static', id: 'white', opacity_stop: 80 },
},
},
// Legacy alias — single-tier emission for v0.2.x consumers.
static: {
kind: 'direct',
ref: { family: 'static', id: 'white' },
},
// Mode-invariant statics. `light` is always #ffffff; `dark` is always
// ~#020203. Useful for surfaces that must NOT swap with theme (brand
// hero sections, splash screens, photography overlays).
static_tiers: {
light: {
kind: 'direct',
ref: { family: 'static', id: 'white' },
},
dark: {
kind: 'direct',
ref: { family: 'static', id: 'dark' },
},
},
},

labels: buildLabels(),
Expand Down
56 changes: 56 additions & 0 deletions packages/tokens/src/generators/semantic-colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,72 @@ function collectEntries(semantics: SemanticsConfig): SemEntry[] {
path: 'backgrounds.neutral.tertiary',
def: semantics.backgrounds.neutral.tertiary,
})
// SPEC §5.1: mode-flipping inverted background (always opposite of mode primary).
out.push({
name: 'bg-inverted',
path: 'backgrounds.neutral.inverted',
def: semantics.backgrounds.neutral.inverted,
})
// SPEC §5.1 / §10.D7: nested-card grouped hierarchy.
out.push({
name: 'bg-grouped-primary',
path: 'backgrounds.neutral.grouped.primary',
def: semantics.backgrounds.neutral.grouped.primary,
})
out.push({
name: 'bg-grouped-secondary',
path: 'backgrounds.neutral.grouped.secondary',
def: semantics.backgrounds.neutral.grouped.secondary,
})
out.push({
name: 'bg-grouped-tertiary',
path: 'backgrounds.neutral.grouped.tertiary',
def: semantics.backgrounds.neutral.grouped.tertiary,
})
// Legacy alias — kept for v0.2.x backward compat.
out.push({
name: 'bg-overlay',
path: 'backgrounds.overlay',
def: semantics.backgrounds.overlay,
})
// SPEC §5.1: 4-tier overlay scrims for modals, tooltips, sheets.
out.push({
name: 'bg-overlay-ghost',
path: 'backgrounds.overlay_tiers.ghost',
def: semantics.backgrounds.overlay_tiers.ghost,
})
out.push({
name: 'bg-overlay-soft',
path: 'backgrounds.overlay_tiers.soft',
def: semantics.backgrounds.overlay_tiers.soft,
})
out.push({
name: 'bg-overlay-base',
path: 'backgrounds.overlay_tiers.base',
def: semantics.backgrounds.overlay_tiers.base,
})
out.push({
name: 'bg-overlay-strong',
path: 'backgrounds.overlay_tiers.strong',
def: semantics.backgrounds.overlay_tiers.strong,
})
// Legacy alias — kept for v0.2.x backward compat.
out.push({
name: 'bg-static',
path: 'backgrounds.static',
def: semantics.backgrounds.static,
})
// SPEC §5.1: mode-invariant statics (always white / always near-black).
out.push({
name: 'bg-static-light',
path: 'backgrounds.static_tiers.light',
def: semantics.backgrounds.static_tiers.light,
})
out.push({
name: 'bg-static-dark',
path: 'backgrounds.static_tiers.dark',
def: semantics.backgrounds.static_tiers.dark,
})

// Labels
const labels = semantics.labels
Expand Down
33 changes: 33 additions & 0 deletions packages/tokens/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,14 +469,47 @@ export interface SemanticDefModeBranch {

// ─── Semantic tree (config.semantics) ───────────────────────────────────

/**
* Backgrounds — surface tokens. Per SPEC §5.1, the full skeleton has:
* Neutral.{Primary, Secondary, Tertiary, Inverted} — 4 base tiers
* Neutral.Grouped.{Primary, Secondary, Tertiary} — nested-card pattern (D7)
* Overlay.{Ghost, Soft, Base, Strong} — scrim/frosted-overlay tiers
* Static.{Light, Dark} — mode-invariant whites/darks
*
* `overlay` and `static` are kept as **single-tier aliases** for backward compat
* with v0.2.x consumers (they collapse to `.base` / `.light` respectively).
* `overlay_tiers` and `static_tiers` carry the full SPEC-required tier sets.
*/
export interface BackgroundsConfig {
neutral: {
primary: SemanticDef
secondary: SemanticDef
tertiary: SemanticDef
/** Mode-flipping background — opposite end of the Gray ladder per mode (SPEC §5.1). */
inverted: SemanticDef
/** Nested-card hierarchy (Apple Settings.app pattern; SPEC §10.D7). */
grouped: {
primary: SemanticDef
secondary: SemanticDef
tertiary: SemanticDef
}
}
/** Legacy single-tier alias (= overlay_tiers.base). Retained for v0.2.x backward compat. */
overlay: SemanticDef
/** Full SPEC §5.1 overlay tiers — frosted-overlay scrims (Light sub-collection alpha-on-white). */
overlay_tiers: {
ghost: SemanticDef
soft: SemanticDef
base: SemanticDef
strong: SemanticDef
}
/** Legacy single-tier alias (= static_tiers.light). Retained for v0.2.x backward compat. */
static: SemanticDef
/** Mode-invariant statics — always emit the same hex regardless of theme. */
static_tiers: {
light: SemanticDef
dark: SemanticDef
}
}

export type TierSet4 = Record<'primary' | 'secondary' | 'tertiary' | 'quaternary', SemanticDef>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1047,8 +1047,18 @@ exports[`G1 · dist/tokens.css byte-stable matches committed snapshot 1`] = `
--bg-primary: oklch(1 0 0);
--bg-secondary: oklch(0.979 0.003 264.5);
--bg-tertiary: oklch(0.943 0.013 286.1);
--bg-inverted: oklch(0.174 0.004 286);
--bg-grouped-primary: oklch(0.979 0.003 264.5);
--bg-grouped-secondary: oklch(1 0 0);
--bg-grouped-tertiary: oklch(0.943 0.013 286.1);
--bg-overlay: oklch(0.08 0 0 / 0.4);
--bg-overlay-ghost: oklch(1 0 0 / 0.01);
--bg-overlay-soft: oklch(1 0 0 / 0.2);
--bg-overlay-base: oklch(1 0 0 / 0.48);
--bg-overlay-strong: oklch(1 0 0 / 0.8);
--bg-static: oklch(1 0 0);
--bg-static-light: oklch(1 0 0);
--bg-static-dark: oklch(0.08 0 0);
--label-neutral-primary: oklch(0.648 0.008 247);
--label-neutral-secondary: oklch(0.742 0.007 247);
--label-neutral-tertiary: oklch(0.83 0.006 247);
Expand Down Expand Up @@ -1874,8 +1884,18 @@ exports[`G1 · dist/tokens.css byte-stable matches committed snapshot 1`] = `
--bg-primary: oklch(1 0 0);
--bg-secondary: oklch(0.976 0.004 271.4);
--bg-tertiary: oklch(0.956 0.009 279.7);
--bg-inverted: oklch(0 0 0);
--bg-grouped-primary: oklch(0.976 0.004 271.4);
--bg-grouped-secondary: oklch(1 0 0);
--bg-grouped-tertiary: oklch(0.956 0.009 279.7);
--bg-overlay: oklch(0.08 0 0 / 0.4);
--bg-overlay-ghost: oklch(1 0 0 / 0.01);
--bg-overlay-soft: oklch(1 0 0 / 0.2);
--bg-overlay-base: oklch(1 0 0 / 0.48);
--bg-overlay-strong: oklch(1 0 0 / 0.8);
--bg-static: oklch(1 0 0);
--bg-static-light: oklch(1 0 0);
--bg-static-dark: oklch(0.08 0 0);
--label-neutral-primary: oklch(0.539 0.008 247);
--label-neutral-secondary: oklch(0.648 0.007 247);
--label-neutral-tertiary: oklch(0.742 0.007 247);
Expand Down Expand Up @@ -2701,8 +2721,18 @@ exports[`G1 · dist/tokens.css byte-stable matches committed snapshot 1`] = `
--bg-primary: oklch(0.174 0.004 286);
--bg-secondary: oklch(0.227 0.004 286.1);
--bg-tertiary: oklch(0.261 0.004 286.1);
--bg-inverted: oklch(1 0 0);
--bg-grouped-primary: oklch(0.227 0.004 286.1);
--bg-grouped-secondary: oklch(0.174 0.004 286);
--bg-grouped-tertiary: oklch(0.261 0.004 286.1);
--bg-overlay: oklch(0.08 0 0 / 0.4);
--bg-overlay-ghost: oklch(1 0 0 / 0.01);
--bg-overlay-soft: oklch(1 0 0 / 0.2);
--bg-overlay-base: oklch(1 0 0 / 0.48);
--bg-overlay-strong: oklch(1 0 0 / 0.8);
--bg-static: oklch(1 0 0);
--bg-static-light: oklch(1 0 0);
--bg-static-dark: oklch(0.08 0 0);
--label-neutral-primary: oklch(0.761 0.006 247);
--label-neutral-secondary: oklch(0.67 0.007 247);
--label-neutral-tertiary: oklch(0.567 0.007 247);
Expand Down Expand Up @@ -3528,8 +3558,18 @@ exports[`G1 · dist/tokens.css byte-stable matches committed snapshot 1`] = `
--bg-primary: oklch(0 0 0);
--bg-secondary: oklch(0.197 0.004 286);
--bg-tertiary: oklch(0.241 0.006 286);
--bg-inverted: oklch(1 0 0);
--bg-grouped-primary: oklch(0.197 0.004 286);
--bg-grouped-secondary: oklch(0 0 0);
--bg-grouped-tertiary: oklch(0.241 0.006 286);
--bg-overlay: oklch(0.08 0 0 / 0.4);
--bg-overlay-ghost: oklch(1 0 0 / 0.01);
--bg-overlay-soft: oklch(1 0 0 / 0.2);
--bg-overlay-base: oklch(1 0 0 / 0.48);
--bg-overlay-strong: oklch(1 0 0 / 0.8);
--bg-static: oklch(1 0 0);
--bg-static-light: oklch(1 0 0);
--bg-static-dark: oklch(0.08 0 0);
--label-neutral-primary: oklch(0.841 0.005 247);
--label-neutral-secondary: oklch(0.759 0.006 247);
--label-neutral-tertiary: oklch(0.668 0.007 247);
Expand Down Expand Up @@ -4356,8 +4396,18 @@ exports[`G1 · dist/tokens.css byte-stable matches committed snapshot 1`] = `
--bg-primary: oklch(0.174 0.004 286);
--bg-secondary: oklch(0.227 0.004 286.1);
--bg-tertiary: oklch(0.261 0.004 286.1);
--bg-inverted: oklch(1 0 0);
--bg-grouped-primary: oklch(0.227 0.004 286.1);
--bg-grouped-secondary: oklch(0.174 0.004 286);
--bg-grouped-tertiary: oklch(0.261 0.004 286.1);
--bg-overlay: oklch(0.08 0 0 / 0.4);
--bg-overlay-ghost: oklch(1 0 0 / 0.01);
--bg-overlay-soft: oklch(1 0 0 / 0.2);
--bg-overlay-base: oklch(1 0 0 / 0.48);
--bg-overlay-strong: oklch(1 0 0 / 0.8);
--bg-static: oklch(1 0 0);
--bg-static-light: oklch(1 0 0);
--bg-static-dark: oklch(0.08 0 0);
--label-neutral-primary: oklch(0.761 0.006 247);
--label-neutral-secondary: oklch(0.67 0.007 247);
--label-neutral-tertiary: oklch(0.567 0.007 247);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -817,8 +817,18 @@ export const pinkA99 = 'var(--pink-a99)';
export const bgPrimary = 'var(--bg-primary)';
export const bgSecondary = 'var(--bg-secondary)';
export const bgTertiary = 'var(--bg-tertiary)';
export const bgInverted = 'var(--bg-inverted)';
export const bgGroupedPrimary = 'var(--bg-grouped-primary)';
export const bgGroupedSecondary = 'var(--bg-grouped-secondary)';
export const bgGroupedTertiary = 'var(--bg-grouped-tertiary)';
export const bgOverlay = 'var(--bg-overlay)';
export const bgOverlayGhost = 'var(--bg-overlay-ghost)';
export const bgOverlaySoft = 'var(--bg-overlay-soft)';
export const bgOverlayBase = 'var(--bg-overlay-base)';
export const bgOverlayStrong = 'var(--bg-overlay-strong)';
export const bgStatic = 'var(--bg-static)';
export const bgStaticLight = 'var(--bg-static-light)';
export const bgStaticDark = 'var(--bg-static-dark)';
export const labelNeutralPrimary = 'var(--label-neutral-primary)';
export const labelNeutralSecondary = 'var(--label-neutral-secondary)';
export const labelNeutralTertiary = 'var(--label-neutral-tertiary)';
Expand Down Expand Up @@ -1952,8 +1962,18 @@ export declare const pinkA99: string;
export declare const bgPrimary: string;
export declare const bgSecondary: string;
export declare const bgTertiary: string;
export declare const bgInverted: string;
export declare const bgGroupedPrimary: string;
export declare const bgGroupedSecondary: string;
export declare const bgGroupedTertiary: string;
export declare const bgOverlay: string;
export declare const bgOverlayGhost: string;
export declare const bgOverlaySoft: string;
export declare const bgOverlayBase: string;
export declare const bgOverlayStrong: string;
export declare const bgStatic: string;
export declare const bgStaticLight: string;
export declare const bgStaticDark: string;
export declare const labelNeutralPrimary: string;
export declare const labelNeutralSecondary: string;
export declare const labelNeutralTertiary: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,18 @@ exports[`Tailwind preset · dist/tailwind-preset.css byte-stable matches committ
--color-bg-primary: var(--bg-primary);
--color-bg-secondary: var(--bg-secondary);
--color-bg-tertiary: var(--bg-tertiary);
--color-bg-inverted: var(--bg-inverted);
--color-bg-grouped-primary: var(--bg-grouped-primary);
--color-bg-grouped-secondary: var(--bg-grouped-secondary);
--color-bg-grouped-tertiary: var(--bg-grouped-tertiary);
--color-bg-overlay: var(--bg-overlay);
--color-bg-overlay-ghost: var(--bg-overlay-ghost);
--color-bg-overlay-soft: var(--bg-overlay-soft);
--color-bg-overlay-base: var(--bg-overlay-base);
--color-bg-overlay-strong: var(--bg-overlay-strong);
--color-bg-static: var(--bg-static);
--color-bg-static-light: var(--bg-static-light);
--color-bg-static-dark: var(--bg-static-dark);
--color-label-neutral-primary: var(--label-neutral-primary);
--color-label-neutral-secondary: var(--label-neutral-secondary);
--color-label-neutral-tertiary: var(--label-neutral-tertiary);
Expand Down
Loading