`, opens in a new tab. */
+ href?: string
+ onClick?: (event: MouseEvent) => void
+}
+
+export interface TabButtonsProps {
+ /** List of options to render. */
+ options?: TabButton[]
+
+ /** @deprecated Use `options` instead. */
+ buttons?: TabButton[]
+
+ modelValue?: TabButtonValue
+ type?: TabButtonsType
+ size?: PillSize
+ vertical?: boolean
+
+ /** Edge the active browser tab attaches to. Only used when `type='browser-tab'` and `vertical`. */
+ direction?: TabButtonsDirection
+}
+
+export interface TabButtonsEmits {
+ 'update:modelValue': [value: TabButtonValue | undefined]
+}
diff --git a/src/components/TextInput/TextInput.vue b/src/components/TextInput/TextInput.vue
index 6214dac85..80589751d 100644
--- a/src/components/TextInput/TextInput.vue
+++ b/src/components/TextInput/TextInput.vue
@@ -189,9 +189,9 @@ const inputClasses = computed(() => {
let variant = props.disabled ? 'disabled' : props.variant
let variantClasses = {
subtle:
- 'border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
+ 'border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:focus-ring',
outline:
- 'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
+ 'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:focus-ring',
disabled: [
'border bg-surface-gray-1 placeholder-ink-gray-3',
props.variant === 'outline'
diff --git a/src/components/Textarea/Textarea.vue b/src/components/Textarea/Textarea.vue
index 43d365dd8..a97ebbf3b 100644
--- a/src/components/Textarea/Textarea.vue
+++ b/src/components/Textarea/Textarea.vue
@@ -133,9 +133,9 @@ const inputClasses = computed(() => {
let variant = props.disabled ? 'disabled' : props.variant
let variantClasses = {
subtle:
- 'border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
+ 'border border-[--surface-gray-2] bg-surface-gray-2 placeholder-ink-gray-4 hover:border-outline-gray-modals hover:bg-surface-gray-3 focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:focus-ring',
outline:
- 'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-outline-gray-3',
+ 'border border-outline-gray-2 bg-surface-white placeholder-ink-gray-4 hover:border-outline-gray-3 hover:shadow-sm focus:bg-surface-white focus:border-outline-gray-4 focus:shadow-sm focus:ring-0 focus-visible:focus-ring',
disabled: [
'border bg-surface-gray-1 placeholder-ink-gray-3',
props.variant === 'outline'
diff --git a/src/components/Toast/Toast.vue b/src/components/Toast/Toast.vue
index 4eaf650c5..c8c278711 100644
--- a/src/components/Toast/Toast.vue
+++ b/src/components/Toast/Toast.vue
@@ -34,7 +34,7 @@
@@ -43,7 +43,7 @@
diff --git a/src/components/Tooltip/Tooltip.api.md b/src/components/Tooltip/Tooltip.api.md
index 7bf3b0d04..86f2f352a 100644
--- a/src/components/Tooltip/Tooltip.api.md
+++ b/src/components/Tooltip/Tooltip.api.md
@@ -23,7 +23,7 @@
name: 'placement',
description: 'Position of the tooltip relative to the trigger.',
required: false,
- type: '"bottom" | "top" | "right" | "left"',
+ type: '"left" | "right" | "bottom" | "top"',
default: '"top"'
},
{
@@ -65,7 +65,7 @@
name: 'side',
description: 'Preferred popover side relative to the trigger.',
required: false,
- type: '"bottom" | "top" | "right" | "left"',
+ type: '"left" | "right" | "bottom" | "top"',
default: '"top"'
},
{
diff --git a/src/components/shared/selection/utils.ts b/src/components/shared/selection/utils.ts
index 95ac18985..ec91d8435 100644
--- a/src/components/shared/selection/utils.ts
+++ b/src/components/shared/selection/utils.ts
@@ -70,10 +70,10 @@ export function triggerVariantClasses(
* on the trigger itself.
*/
export const triggerBaseClassesFocusVisible =
- 'relative inline-flex items-center gap-2 text-left text-ink-gray-7 outline-none transition-[background-color,border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-visible:ring-2 data-[state=open]:ring-2 ring-outline-gray-3'
+ 'relative inline-flex items-center gap-2 text-left text-ink-gray-7 outline-none transition-[background-color,border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-visible:focus-ring data-[state=open]:focus-ring'
export const triggerBaseClassesFocusWithin =
- 'relative inline-flex items-center gap-2 text-left text-ink-gray-7 outline-none transition-[background-color,border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-within:ring-2 data-[state=open]:ring-2 ring-outline-gray-3'
+ 'relative inline-flex items-center gap-2 text-left text-ink-gray-7 outline-none transition-[background-color,border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-within:focus-ring data-[state=open]:focus-ring'
export const itemClasses =
'select-none rounded border-0 text-base text-ink-gray-9 transition-colors duration-100 ease-out data-[disabled]:text-ink-gray-4 data-[highlighted]:bg-surface-gray-2 data-[state=checked]:bg-surface-gray-3 data-[highlighted]:data-[state=checked]:bg-surface-gray-4'
diff --git a/src/index.ts b/src/index.ts
index b687c166c..765e0bd92 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -18,6 +18,7 @@ export * from './components/Divider'
export * from './components/Dropdown'
export * from './components/ErrorMessage'
export { default as FeatherIcon } from './components/FeatherIcon.vue'
+export * from './components/Icon'
export * from './components/FileUploader'
export * from './components/FormControl'
export { default as FormLabel } from './components/FormLabel.vue'
@@ -45,7 +46,7 @@ export * from './components/MonthPicker'
export * from './components/MultiSelect'
export * from './components/Password'
export * from './components/Popover'
-export * from './components/Progress'
+export * from './components/Pill'
export * from './components/Rating'
export { default as Resource } from './components/Resource.vue'
export * from './components/Select'
diff --git a/tailwind/colorPalette.js b/tailwind/colorPalette.js
index cfda87923..475ef6708 100644
--- a/tailwind/colorPalette.js
+++ b/tailwind/colorPalette.js
@@ -1,5 +1,6 @@
import tailwindColors from 'tailwindcss/colors'
import colorsData from './colors.json'
+import effectsData from './generated/effects.json'
function generateColorPalette() {
const colorPalette = {
@@ -129,4 +130,41 @@ function generateSemanticColors() {
return output
}
-export { generateColorPalette, generateCSSVariables, generateSemanticColors }
+// Emit `--elevation-*` and `--focus-*` CSS variables. Elevation uses the
+// Figma `light/*` values in both modes (matches how Espresso 2.0 actually
+// applies shadows in dark mode — see the dark-mode page in Figma, which
+// references `elevation/light/*` exclusively). The Figma `dark/*` set is
+// exposed as `--dark-elevation-*` for opt-in use via `shadow-dark-*`.
+// Focus rings still mode-swap. Theme-independent entries (e.g.
+// `elevation.custom.status`) land in `:root` only.
+function generateEffectVariables() {
+ const output = {
+ ':root': {},
+ '[data-theme="dark"]': {},
+ }
+
+ for (const [step, value] of Object.entries(effectsData.elevation.light)) {
+ output[':root'][`--elevation-${step}`] = value
+ }
+ for (const [step, value] of Object.entries(effectsData.elevation.dark)) {
+ output[':root'][`--dark-elevation-${step}`] = value
+ }
+ for (const [name, value] of Object.entries(effectsData.elevation.custom)) {
+ output[':root'][`--elevation-${name}`] = value
+ }
+ for (const [name, value] of Object.entries(effectsData.focus.light)) {
+ output[':root'][`--focus-${name}`] = value
+ }
+ for (const [name, value] of Object.entries(effectsData.focus.dark)) {
+ output['[data-theme="dark"]'][`--focus-${name}`] = value
+ }
+
+ return output
+}
+
+export {
+ generateColorPalette,
+ generateCSSVariables,
+ generateSemanticColors,
+ generateEffectVariables,
+}
diff --git a/tailwind/colors.json b/tailwind/colors.json
index 5ececaedb..c8c9885f9 100644
--- a/tailwind/colors.json
+++ b/tailwind/colors.json
@@ -1,355 +1,397 @@
{
"lightMode": {
"gray": {
- "50": "#F8F8F8",
- "100": "#F3F3F3",
- "200": "#EDEDED",
- "300": "#E2E2E2",
- "400": "#C7C7C7",
+ "50": "#f8f8f8",
+ "100": "#f3f3f3",
+ "200": "#ededed",
+ "300": "#e2e2e2",
+ "400": "#c7c7c7",
"500": "#999999",
- "600": "#7C7C7C",
+ "600": "#7c7c7c",
"700": "#525252",
"800": "#383838",
- "900": "#171717"
+ "900": "#171717",
+ "950": "#0f0f0f"
},
"blue": {
- "50": "#F2F9FF",
- "100": "#E6F4FF",
- "200": "#C8E6FF",
- "300": "#A7D7FD",
- "400": "#73BBF6",
- "500": "#0289F7",
- "600": "#007BE0",
- "700": "#0070CC",
- "800": "#005CA3",
- "900": "#004880"
+ "50": "#f0f7fc",
+ "100": "#e6f4ff",
+ "200": "#c8e6ff",
+ "300": "#a7d7fd",
+ "400": "#73bbf6",
+ "500": "#0289f7",
+ "600": "#007be0",
+ "700": "#0070cc",
+ "800": "#005ca3",
+ "900": "#004880",
+ "950": "#032e63"
},
"green": {
- "50": "#F2FDF4",
- "100": "#E4FAEB",
- "200": "#C3F9D3",
- "300": "#A6EFC0",
- "400": "#86E0A8",
- "500": "#46B37E",
- "600": "#278F5E",
+ "50": "#f1fbf5",
+ "100": "#e4faeb",
+ "200": "#c3f9d3",
+ "300": "#a6efc0",
+ "400": "#78e09f",
+ "500": "#46b37e",
+ "600": "#268c5c",
"700": "#137949",
- "800": "#075E35",
- "900": "#173B2C"
+ "800": "#075e35",
+ "900": "#173b2c",
+ "950": "#0a3020"
},
"red": {
- "50": "#FFF7F7",
- "100": "#FFE7E7",
- "200": "#FFD8D8",
- "300": "#FDC2C2",
- "400": "#F79596",
- "500": "#E03636",
- "600": "#CC2929",
- "700": "#B52A2A",
- "800": "#941F1F",
- "900": "#6B1515"
- },
- "amber": {
- "50": "#FDFAED",
- "100": "#FFF7D3",
- "200": "#FEEDA9",
- "300": "#FBDB73",
- "400": "#FBCC55",
- "500": "#E79913",
- "600": "#DB7706",
- "700": "#B35309",
- "800": "#91400D",
- "900": "#763813"
+ "50": "#fff7f7",
+ "100": "#ffe7e7",
+ "200": "#ffd8d8",
+ "300": "#fdc2c2",
+ "400": "#f79596",
+ "500": "#e03636",
+ "600": "#cc2929",
+ "700": "#b52a2a",
+ "800": "#941f1f",
+ "900": "#6b1515",
+ "950": "#4c0d0d"
},
"orange": {
- "50": "#FFF9F5",
- "100": "#FFEFE4",
- "200": "#FFDEC5",
- "300": "#FFCBA3",
- "400": "#F4B07F",
- "500": "#E86C13",
- "600": "#D45A08",
- "700": "#BD3E0C",
- "800": "#9E3513",
- "900": "#6B2711"
+ "50": "#fff4ed",
+ "100": "#ffefe4",
+ "200": "#ffdec5",
+ "300": "#ffcba3",
+ "400": "#f4b07f",
+ "500": "#e86c13",
+ "600": "#d35a09",
+ "700": "#bd3e0c",
+ "800": "#9e3513",
+ "900": "#6b2711",
+ "950": "#491605"
+ },
+ "amber": {
+ "50": "#fdfaed",
+ "100": "#fff7d3",
+ "200": "#feeda9",
+ "300": "#fbdb73",
+ "400": "#fbc53f",
+ "500": "#e79913",
+ "600": "#d47408",
+ "700": "#b35309",
+ "800": "#91400d",
+ "900": "#763813",
+ "950": "#4e2209"
},
"yellow": {
- "50": "#FFFCEF",
- "100": "#FFF7D3",
- "200": "#F7E9A8",
- "300": "#F5E171",
- "400": "#F2D14B",
- "500": "#EDBA13",
- "600": "#D1930D",
- "700": "#AB6E05",
- "800": "#8C5600",
- "900": "#733F12"
+ "50": "#fcfbe8",
+ "100": "#fff7d3",
+ "200": "#f7e9a8",
+ "300": "#f5e171",
+ "400": "#f2d14b",
+ "500": "#edba13",
+ "600": "#ab790d",
+ "700": "#9a6304",
+ "800": "#8c5600",
+ "900": "#733f12",
+ "950": "#512a09"
},
"teal": {
- "50": "#F0FDFA",
- "100": "#E6F7F4",
- "200": "#BAE8E1",
- "300": "#97DED4",
- "400": "#73D1C4",
- "500": "#36BAAD",
- "600": "#0B9E92",
- "700": "#0F736B",
- "800": "#115C57",
- "900": "#114541"
+ "50": "#eefbf8",
+ "100": "#e6f7f4",
+ "200": "#bae8e1",
+ "300": "#97ded4",
+ "400": "#73d1c4",
+ "500": "#36baad",
+ "600": "#0a857b",
+ "700": "#0f736b",
+ "800": "#115c57",
+ "900": "#114541",
+ "950": "#053734"
},
"cyan": {
- "50": "#F5FBFC",
- "100": "#DDF7FF",
- "200": "#B3E8F7",
- "300": "#99E2F8",
- "400": "#72D5F3",
- "500": "#3BBDE5",
- "600": "#32A4C7",
- "700": "#267A94",
- "800": "#125C73",
- "900": "#164759"
+ "50": "#f2fbfd",
+ "100": "#ddf7ff",
+ "200": "#b3e8f7",
+ "300": "#99e2f8",
+ "400": "#72d5f3",
+ "500": "#3bbde5",
+ "600": "#1f8cad",
+ "700": "#1d7f9d",
+ "800": "#125c73",
+ "900": "#164759",
+ "950": "#05383f"
},
"purple": {
- "50": "#FDFAFF",
- "100": "#F6E9FF",
- "200": "#ECD3FF",
- "300": "#E2B9FC",
- "400": "#CFA1F2",
- "500": "#9C45E3",
- "600": "#8642C2",
- "700": "#6E399D",
- "800": "#5C2F83",
- "900": "#401863"
+ "50": "#f8f3fb",
+ "100": "#f6e9ff",
+ "200": "#ecd3ff",
+ "300": "#e2b9fc",
+ "400": "#bf78fa",
+ "500": "#9c45e3",
+ "600": "#8e49ca",
+ "700": "#6e399d",
+ "800": "#5c2f83",
+ "900": "#401863",
+ "950": "#2d084e"
},
"pink": {
- "50": "#FFF7FC",
- "100": "#FDE8F5",
- "200": "#FFD5F0",
- "300": "#F9B9E0",
- "400": "#F6A7D6",
- "500": "#E34AA6",
- "600": "#CF3A96",
- "700": "#9C2671",
+ "50": "#fff7fc",
+ "100": "#fde8f5",
+ "200": "#ffd5f0",
+ "300": "#f9b9e0",
+ "400": "#f77cc6",
+ "500": "#e34aa6",
+ "600": "#cf3a96",
+ "700": "#9c2671",
"800": "#801458",
- "900": "#570F3E"
+ "900": "#570f3e",
+ "950": "#40062c"
},
"violet": {
- "50": "#FBFAFF",
- "100": "#F0EBFF",
- "200": "#DBD5FF",
- "300": "#C9BAFB",
- "400": "#B3A1F5",
- "500": "#6846E3",
- "600": "#5F46C7",
- "700": "#4F3DA1",
+ "50": "#f5f3fc",
+ "100": "#eee8ff",
+ "200": "#dbd5ff",
+ "300": "#c9bafb",
+ "400": "#a68efe",
+ "500": "#7757ee",
+ "600": "#6b53d0",
+ "700": "#4f3da1",
"800": "#392980",
- "900": "#251959"
+ "900": "#251959",
+ "950": "#150655"
+ },
+ "gray-alpha": {
+ "50": "#00000008",
+ "100": "#0000000b",
+ "200": "#00000012",
+ "300": "#0000001d",
+ "400": "#00000038",
+ "500": "#00000066",
+ "600": "#00000083",
+ "700": "#000000ad",
+ "800": "#000000c7",
+ "900": "#000000e8",
+ "950": "#000000f0"
}
},
"darkMode": {
"gray": {
- "50": "#F8F8F8",
- "100": "#D4D4D4",
- "200": "#AFAFAF",
- "250": "#999999",
+ "50": "#f8f8f8",
+ "100": "#d9d9d9",
+ "200": "#afafaf",
"300": "#808080",
"400": "#717171",
+ "450": "#575757",
"500": "#424242",
"600": "#343434",
- "650": "#2B2B2B",
- "700": "#232323",
- "800": "#1C1C1C",
- "900": "#0F0F0F"
+ "700": "#2b2b2b",
+ "800": "#242424",
+ "900": "#1f1f1f",
+ "950": "#171717"
},
"blue": {
- "50": "#C9E0F5",
- "100": "#ADD2F5",
- "200": "#8CC1EC",
- "300": "#5AAEF2",
- "400": "#3294E3",
- "500": "#1580D8",
+ "50": "#c9e0f5",
+ "100": "#add2f5",
+ "200": "#8cc1ec",
+ "300": "#5aaef2",
+ "400": "#2a8edf",
+ "500": "#1580d8",
"600": "#155999",
- "700": "#063D71",
- "800": "#052B53",
- "900": "#0E2037",
- "900-80": "#0E2037CC"
+ "700": "#063d71",
+ "800": "#052b53",
+ "900": "#10233d",
+ "950": "#0e1c2f"
},
"green": {
- "50": "#C8F3DE",
- "100": "#9BE6C1",
- "200": "#78D7A9",
- "300": "#58C08E",
- "400": "#1BA964",
- "500": "#0A9752",
- "600": "#0F814A",
+ "50": "#c8f3de",
+ "100": "#9be6c1",
+ "200": "#78d7a9",
+ "300": "#58c08e",
+ "400": "#469170",
+ "500": "#148950",
+ "600": "#077241",
"700": "#035831",
- "800": "#0A3F27",
- "900": "#0B2E1C"
+ "800": "#0a3f27",
+ "900": "#0c2d1c",
+ "950": "#0b1e14"
},
"red": {
- "50": "#FFC1C1",
- "100": "#FF9595",
- "200": "#FC7474",
- "300": "#EB4D52",
- "400": "#E43838",
- "500": "#C12020",
- "600": "#901818",
- "700": "#681916",
+ "50": "#ffdede",
+ "100": "#ffc1c1",
+ "200": "#fe7c7c",
+ "300": "#eb4d52",
+ "400": "#ce5a5a",
+ "500": "#b01f1f",
+ "600": "#862020",
+ "700": "#661717",
"800": "#521515",
- "900": "#361515",
- "800-90": "#521515E6",
- "900-90": "#361515E6"
- },
- "amber": {
- "50": "#F9E8A5",
- "100": "#F8D16E",
- "200": "#F0BA31",
- "300": "#E79913",
- "400": "#E37D00",
- "500": "#CB6D10",
- "600": "#824108",
- "700": "#603007",
- "800": "#4B2606",
- "900": "#371E06"
+ "900": "#441316",
+ "950": "#271111"
},
"orange": {
- "50": "#FFCDAD",
- "100": "#FFA873",
- "200": "#FA8A40",
- "300": "#DE6D1B",
- "400": "#C45A0E",
+ "50": "#ffcdad",
+ "100": "#ffa873",
+ "200": "#fa8a40",
+ "300": "#e3701c",
+ "400": "#e16914",
"500": "#984509",
"600": "#823906",
"700": "#683108",
"800": "#532707",
- "900": "#401F07",
- "900-80": "#401F07CC"
+ "900": "#361c09",
+ "950": "#2b1708"
+ },
+ "amber": {
+ "50": "#ffe59a",
+ "100": "#f4c25f",
+ "200": "#ffaa3e",
+ "300": "#fa961f",
+ "400": "#c87a2d",
+ "500": "#bd660f",
+ "600": "#975215",
+ "700": "#753a07",
+ "800": "#4b2606",
+ "900": "#371e06",
+ "950": "#281808"
},
"yellow": {
- "50": "#FFE89D",
- "100": "#F8D76A",
- "200": "#ECC02E",
- "300": "#DAAE15",
- "400": "#C69C12",
- "500": "#9C7A0A",
- "600": "#705606",
- "700": "#5B4605",
- "800": "#3F3004",
- "900": "#322604"
+ "50": "#ffeeb8",
+ "100": "#ffe386",
+ "200": "#f8d76a",
+ "300": "#ecc02e",
+ "400": "#bb972a",
+ "500": "#9c7e1c",
+ "600": "#99780a",
+ "700": "#705606",
+ "800": "#5b4605",
+ "900": "#3a2c04",
+ "950": "#261d03"
},
"teal": {
- "50": "#93F2E8",
- "100": "#6EE7DB",
- "200": "#52DACC",
- "300": "#3DC6B8",
- "400": "#219C8F",
- "500": "#1B7169",
- "600": "#13564F",
- "700": "#0C423C",
- "800": "#0B3A35",
- "900": "#0A2D29"
+ "50": "#a0f7ed",
+ "100": "#7ef3e7",
+ "200": "#51decf",
+ "300": "#28bcac",
+ "400": "#2ca094",
+ "500": "#1b7169",
+ "600": "#145b54",
+ "700": "#0b4942",
+ "800": "#0b3a35",
+ "900": "#0a2d29",
+ "950": "#0b2320"
},
"cyan": {
- "50": "#D0F0FA",
- "100": "#A0E6F7",
- "200": "#68D3F3",
- "300": "#3CB8DC",
- "400": "#2B8DAB",
- "500": "#23728B",
- "600": "#155266",
- "700": "#0E3B49",
- "800": "#0D2B36",
- "900": "#0B252D"
+ "50": "#d0f0fa",
+ "100": "#95e3f6",
+ "200": "#62cae9",
+ "300": "#3cb8dc",
+ "400": "#249cc2",
+ "500": "#107b9b",
+ "600": "#0c6783",
+ "700": "#104f62",
+ "800": "#0d3b49",
+ "900": "#0b2932",
+ "950": "#0b2028"
},
"purple": {
- "50": "#E5C6FB",
- "100": "#D9AFF5",
- "200": "#C993EF",
- "300": "#B168E8",
- "400": "#984BD8",
- "500": "#7A2DB9",
- "600": "#591F89",
- "700": "#47176E",
+ "50": "#e5c6fb",
+ "100": "#d9aff5",
+ "200": "#c993ef",
+ "300": "#b168e8",
+ "400": "#a26fce",
+ "500": "#7a2db9",
+ "600": "#622195",
+ "700": "#491870",
"800": "#391457",
- "900": "#2E1146"
+ "900": "#2c1042",
+ "950": "#23132f"
},
"pink": {
- "50": "#F6C5DE",
- "100": "#F69AD1",
- "200": "#ED77BE",
- "300": "#E359AB",
- "400": "#CB4394",
- "500": "#AC377D",
- "600": "#822A5F",
- "700": "#68204B",
- "800": "#601D46",
- "900": "#471432",
- "900-80": "#471432CC"
+ "50": "#ffbbe4",
+ "100": "#f69ad1",
+ "200": "#ed77be",
+ "300": "#e359ab",
+ "400": "#cb5d9e",
+ "500": "#ac377d",
+ "600": "#892660",
+ "700": "#6f1d4d",
+ "800": "#5b183f",
+ "900": "#42132f",
+ "950": "#2e0f22"
},
"violet": {
- "50": "#DACBF7",
- "100": "#C4AFEE",
- "200": "#B398EF",
- "300": "#9D7CEA",
- "400": "#8867E8",
- "500": "#5C3FC2",
- "600": "#4639A6",
- "700": "#332978",
- "800": "#281E5D",
- "900": "#221C42"
+ "50": "#cdbeff",
+ "100": "#bca9fc",
+ "200": "#9f87ed",
+ "300": "#9478f8",
+ "400": "#9683d8",
+ "500": "#785fce",
+ "600": "#403397",
+ "700": "#3d3286",
+ "800": "#291d64",
+ "900": "#1f1841",
+ "950": "#18142e"
+ },
+ "gray-alpha": {
+ "50": "#fffffff7",
+ "100": "#ffffffd1",
+ "200": "#ffffffab",
+ "300": "#ffffff78",
+ "400": "#ffffff69",
+ "450": "#ffffff4d",
+ "500": "#ffffff36",
+ "600": "#ffffff26",
+ "700": "#ffffff1f",
+ "800": "#ffffff14",
+ "900": "#ffffff0f",
+ "950": "#0000000a"
+ },
+ "red-alpha": {
+ "50": "#ffdede",
+ "100": "#ffc1c1",
+ "200": "#fe7c7c",
+ "300": "#ff5858f0",
+ "400": "#fa3c3cd9",
+ "500": "#ed2222ba",
+ "600": "#ed2d2d8a",
+ "700": "#c120207d",
+ "800": "#c01b1b61",
+ "900": "#5f16168f",
+ "950": "#53060666"
}
},
"overlay": {
"white": {
- "50": "#FFFFFF1A",
- "100": "#FFFFFF2E",
- "200": "#FFFFFF45",
- "300": "#FFFFFF5C",
- "400": "#FFFFFF73",
- "500": "#FFFFFF8A",
- "600": "#FFFFFFA1",
- "700": "#FFFFFFB8",
- "800": "#FFFFFFCF",
- "900": "#FFFFFFE6"
+ "50": "#ffffff1a",
+ "100": "#ffffff2e",
+ "200": "#ffffff45",
+ "300": "#ffffff5c",
+ "400": "#ffffff73",
+ "500": "#ffffff8a",
+ "600": "#ffffffa1",
+ "700": "#ffffffb8",
+ "800": "#ffffffcf",
+ "900": "#ffffffe5",
+ "950": "#fffffff2"
},
"black": {
"50": "#00000017",
- "100": "#0000002E",
+ "100": "#0000002e",
"200": "#00000045",
- "300": "#0000005C",
+ "300": "#0000005c",
"400": "#00000073",
- "500": "#0000008A",
- "600": "#000000A1",
- "700": "#000000B8",
- "800": "#000000CF",
- "900": "#000000E6"
+ "500": "#0000008a",
+ "600": "#000000a1",
+ "700": "#000000b8",
+ "800": "#000000cf",
+ "900": "#000000e5",
+ "950": "#000000f2"
}
},
"neutral": {
- "white": "#FFFFFF",
+ "white": "#ffffff",
"black": "#000000"
},
"themedVariables": {
"light": {
- "outline": {
- "white": "neutral/white",
- "gray-1": "lightMode/gray/200",
- "gray-2": "lightMode/gray/300",
- "gray-3": "lightMode/gray/400",
- "gray-4": "lightMode/gray/500",
- "gray-5": "lightMode/gray/800",
- "red-1": "lightMode/red/300",
- "red-2": "lightMode/red/400",
- "red-3": "lightMode/red/500",
- "green-1": "lightMode/green/300",
- "green-2": "lightMode/green/400",
- "amber-1": "lightMode/amber/300",
- "amber-2": "lightMode/amber/400",
- "blue-1": "lightMode/blue/300",
- "orange-1": "lightMode/orange/400",
- "gray-modals": "lightMode/gray/200"
- },
"surface": {
- "white": "neutral/white",
+ "base": "neutral/white",
"gray-1": "lightMode/gray/50",
"gray-2": "lightMode/gray/100",
"gray-3": "lightMode/gray/200",
@@ -357,6 +399,10 @@
"gray-5": "lightMode/gray/700",
"gray-6": "lightMode/gray/800",
"gray-7": "lightMode/gray/900",
+ "menu-bar": "lightMode/gray/50",
+ "base-contrast": "neutral/white",
+ "gray-1-contrast": "neutral/white",
+ "gray-2-contrast": "neutral/white",
"red-1": "lightMode/red/50",
"red-2": "lightMode/red/100",
"red-3": "lightMode/red/200",
@@ -366,133 +412,263 @@
"red-7": "lightMode/red/800",
"green-1": "lightMode/green/50",
"green-2": "lightMode/green/100",
- "green-3": "lightMode/green/600",
+ "green-3": "lightMode/green/200",
+ "green-4": "lightMode/green/300",
+ "green-5": "lightMode/green/600",
+ "green-6": "lightMode/green/700",
+ "green-7": "lightMode/green/800",
"amber-1": "lightMode/amber/50",
"amber-2": "lightMode/amber/100",
- "amber-3": "lightMode/amber/600",
+ "amber-3": "lightMode/amber/200",
+ "amber-4": "lightMode/amber/300",
+ "amber-5": "lightMode/amber/600",
+ "amber-6": "lightMode/amber/700",
+ "amber-7": "lightMode/amber/800",
"blue-1": "lightMode/blue/50",
"blue-2": "lightMode/blue/100",
- "blue-3": "lightMode/blue/600",
- "orange-1": "lightMode/orange/100",
- "violet-1": "lightMode/violet/100",
+ "blue-3": "lightMode/blue/200",
+ "blue-4": "lightMode/blue/300",
+ "blue-5": "lightMode/blue/600",
+ "blue-6": "lightMode/blue/700",
+ "blue-7": "lightMode/blue/800",
+ "orange-2": "lightMode/orange/100",
+ "violet-2": "lightMode/violet/100",
+ "violet-3": "lightMode/violet/200",
+ "violet-4": "lightMode/violet/300",
+ "violet-5": "lightMode/violet/600",
+ "violet-6": "lightMode/violet/700",
+ "violet-7": "lightMode/violet/800",
+ "cyan-2": "lightMode/cyan/100",
+ "alert-button-default": "neutral/white",
+ "alert-button-info": "neutral/white",
+ "alert-button-success": "neutral/white",
+ "alert-button-warning": "neutral/white",
+ "alert-button-error": "neutral/white",
+ "white": "neutral/white",
+ "modal": "neutral/white",
+ "selected": "neutral/white",
+ "cards": "neutral/white",
"cyan-1": "lightMode/cyan/100",
"pink-1": "lightMode/pink/100",
- "menu-bar": "lightMode/gray/50",
- "cards": "neutral/white",
- "modal": "neutral/white",
- "selected": "neutral/white"
+ "orange-1": "lightMode/orange/100",
+ "violet-1": "lightMode/violet/100"
},
"ink": {
- "white": "neutral/white",
+ "base": "neutral/white",
"gray-1": "lightMode/gray/200",
"gray-2": "lightMode/gray/300",
"gray-3": "lightMode/gray/400",
"gray-4": "lightMode/gray/500",
"gray-5": "lightMode/gray/600",
"gray-6": "lightMode/gray/700",
- "gray-7": "lightMode/gray/700",
- "gray-8": "lightMode/gray/800",
- "gray-9": "lightMode/gray/900",
+ "gray-7": "lightMode/gray/800",
+ "gray-8": "lightMode/gray/900",
"red-1": "lightMode/red/50",
"red-2": "lightMode/red/400",
"red-3": "lightMode/red/500",
- "red-4": "lightMode/red/600",
+ "red-4": "lightMode/red/700",
"green-1": "lightMode/green/50",
- "green-2": "lightMode/green/500",
- "green-3": "lightMode/green/600",
+ "green-2": "lightMode/green/400",
+ "green-3": "lightMode/green/500",
+ "green-4": "lightMode/green/700",
+ "green-6": "lightMode/green/600",
"amber-1": "lightMode/amber/50",
- "amber-2": "lightMode/amber/500",
- "amber-3": "lightMode/amber/600",
+ "amber-2": "lightMode/amber/400",
+ "amber-3": "lightMode/amber/500",
+ "amber-4": "lightMode/amber/700",
"blue-1": "lightMode/blue/50",
- "blue-2": "lightMode/blue/500",
- "blue-3": "lightMode/blue/600",
+ "blue-2": "lightMode/blue/400",
+ "blue-3": "lightMode/blue/500",
+ "blue-4": "lightMode/blue/700",
+ "cyan-3": "lightMode/cyan/500",
+ "violet-1": "lightMode/violet/50",
+ "violet-2": "lightMode/violet/400",
+ "violet-3": "lightMode/violet/500",
+ "violet-4": "lightMode/violet/700",
+ "blue-link": "lightMode/blue/400",
+ "alert-button-default": "lightMode/gray/900",
+ "alert-button-info": "lightMode/gray/900",
+ "alert-button-success": "lightMode/gray/900",
+ "alert-button-warning": "lightMode/gray/900",
+ "alert-button-error": "lightMode/gray/900",
+ "white": "neutral/white",
+ "gray-9": "lightMode/gray/900",
"cyan-1": "lightMode/cyan/500",
- "pink-1": "lightMode/pink/500",
- "violet-1": "lightMode/violet/500",
- "blue-link": "lightMode/blue/400"
+ "pink-1": "lightMode/pink/500"
+ },
+ "outline": {
+ "base": "neutral/white",
+ "gray-1": "lightMode/gray/200",
+ "gray-1-contrast": "lightMode/gray/200",
+ "gray-2": "lightMode/gray/300",
+ "gray-3": "lightMode/gray/400",
+ "gray-4": "lightMode/gray/500",
+ "gray-5": "lightMode/gray/800",
+ "red-2": "lightMode/red/300",
+ "red-3": "lightMode/red/400",
+ "red-4": "lightMode/red/500",
+ "green-2": "lightMode/green/300",
+ "green-3": "lightMode/green/400",
+ "green-4": "lightMode/green/500",
+ "amber-2": "lightMode/amber/300",
+ "amber-3": "lightMode/amber/400",
+ "amber-4": "lightMode/amber/500",
+ "blue-2": "lightMode/blue/300",
+ "blue-3": "lightMode/blue/400",
+ "blue-4": "lightMode/blue/500",
+ "orange-3": "lightMode/orange/400",
+ "violet-2": "lightMode/violet/300",
+ "violet-3": "lightMode/violet/400",
+ "violet-4": "lightMode/violet/500",
+ "gray-modal": "lightMode/gray/200",
+ "gray-modals": "lightMode/gray/200",
+ "white": "neutral/white",
+ "blue-1": "lightMode/blue/300",
+ "red-1": "lightMode/red/300",
+ "green-1": "lightMode/green/200",
+ "amber-1": "lightMode/amber/200",
+ "orange-1": "lightMode/orange/200"
}
},
"dark": {
- "outline": {
- "white": "darkMode/gray/800",
- "gray-1": "darkMode/gray/700",
- "gray-2": "darkMode/gray/600",
- "gray-3": "darkMode/gray/500",
- "gray-4": "darkMode/gray/300",
- "gray-5": "lightMode/gray/200",
- "red-1": "darkMode/red/800",
- "red-2": "darkMode/red/700",
- "red-3": "darkMode/red/600",
- "green-1": "darkMode/green/800",
- "green-2": "darkMode/green/700",
- "amber-1": "darkMode/amber/800",
- "amber-2": "darkMode/amber/700",
- "blue-1": "darkMode/blue/800",
- "orange-1": "darkMode/orange/700",
- "gray-modals": "darkMode/gray/600"
- },
"surface": {
- "white": "darkMode/gray/900",
- "gray-1": "darkMode/gray/700",
- "gray-2": "darkMode/gray/650",
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-2": "darkMode/gray/700",
"gray-3": "darkMode/gray/600",
"gray-4": "darkMode/gray/500",
"gray-5": "darkMode/gray/200",
"gray-6": "darkMode/gray/100",
"gray-7": "darkMode/gray/50",
- "red-1": "darkMode/red/900",
- "red-2": "darkMode/red/900-90",
- "red-3": "darkMode/red/800-90",
+ "menu-bar": "darkMode/gray/950",
+ "base-contrast": "darkMode/gray/900",
+ "gray-1-contrast": "darkMode/gray/900",
+ "gray-2-contrast": "darkMode/gray/600",
+ "red-1": "darkMode/red/950",
+ "red-2": "darkMode/red/900",
+ "red-3": "darkMode/red/800",
"red-4": "darkMode/red/700",
- "red-5": "darkMode/red/400",
- "red-6": "darkMode/red/500",
+ "red-5": "darkMode/red/500",
+ "red-6": "darkMode/red/400",
"red-7": "darkMode/red/600",
"green-1": "darkMode/green/900",
- "green-2": "darkMode/green/800",
- "green-3": "darkMode/green/400",
- "amber-1": "darkMode/amber/900",
- "amber-2": "darkMode/amber/800",
- "amber-3": "darkMode/amber/400",
- "blue-1": "darkMode/blue/900",
- "blue-2": "darkMode/blue/800",
- "blue-3": "darkMode/blue/400",
- "orange-1": "darkMode/orange/900-80",
- "violet-1": "darkMode/violet/900",
- "cyan-1": "darkMode/cyan/900",
- "pink-1": "darkMode/pink/900-80",
- "menu-bar": "darkMode/gray/900",
- "cards": "darkMode/gray/800",
+ "green-2": "darkMode/green/900",
+ "green-3": "darkMode/green/800",
+ "green-4": "darkMode/green/700",
+ "green-5": "darkMode/green/500",
+ "green-6": "darkMode/green/400",
+ "green-7": "darkMode/green/600",
+ "amber-1": "darkMode/amber/950",
+ "amber-2": "darkMode/amber/900",
+ "amber-3": "darkMode/amber/800",
+ "amber-4": "darkMode/amber/700",
+ "amber-5": "darkMode/amber/500",
+ "amber-6": "darkMode/amber/400",
+ "amber-7": "darkMode/amber/600",
+ "blue-1": "darkMode/blue/950",
+ "blue-2": "darkMode/blue/900",
+ "blue-3": "darkMode/blue/800",
+ "blue-4": "darkMode/blue/700",
+ "blue-5": "darkMode/blue/500",
+ "blue-6": "darkMode/blue/400",
+ "blue-7": "darkMode/blue/600",
+ "orange-2": "darkMode/orange/900",
+ "violet-2": "darkMode/violet/900",
+ "violet-3": "darkMode/violet/800",
+ "violet-4": "darkMode/violet/700",
+ "violet-5": "darkMode/violet/500",
+ "violet-6": "darkMode/violet/400",
+ "violet-7": "darkMode/violet/600",
+ "cyan-2": "darkMode/cyan/900",
+ "alert-button-default": "darkMode/gray/500",
+ "alert-button-info": "darkMode/blue/700",
+ "alert-button-success": "darkMode/green/700",
+ "alert-button-warning": "darkMode/amber/700",
+ "alert-button-error": "darkMode/red/700",
+ "white": "darkMode/gray/900",
"modal": "darkMode/gray/700",
- "selected": "darkMode/gray/500"
+ "selected": "darkMode/gray/500",
+ "cards": "darkMode/gray/800",
+ "cyan-1": "darkMode/cyan/900",
+ "pink-1": "darkMode/pink/900",
+ "orange-1": "darkMode/orange/900",
+ "violet-1": "darkMode/violet/900"
},
"ink": {
- "white": "darkMode/gray/900",
- "gray-1": "darkMode/gray/700",
- "gray-2": "darkMode/gray/500",
- "gray-3": "darkMode/gray/400",
- "gray-4": "darkMode/gray/400",
- "gray-5": "darkMode/gray/300",
- "gray-6": "darkMode/gray/250",
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-2": "darkMode/gray/600",
+ "gray-3": "darkMode/gray/500",
+ "gray-4": "darkMode/gray/450",
+ "gray-5": "darkMode/gray/400",
+ "gray-6": "darkMode/gray/300",
"gray-7": "darkMode/gray/200",
"gray-8": "darkMode/gray/100",
- "gray-9": "darkMode/gray/50",
"red-1": "neutral/white",
"red-2": "darkMode/red/700",
"red-3": "darkMode/red/400",
- "red-4": "darkMode/red/200",
+ "red-4": "darkMode/red/300",
"green-1": "neutral/white",
- "green-2": "darkMode/green/400",
- "green-3": "darkMode/green/300",
+ "green-2": "darkMode/green/700",
+ "green-3": "darkMode/green/400",
+ "green-4": "darkMode/green/300",
+ "green-6": "darkMode/green/400",
"amber-1": "neutral/white",
- "amber-2": "darkMode/amber/400",
- "amber-3": "darkMode/amber/300",
+ "amber-2": "darkMode/amber/700",
+ "amber-3": "darkMode/amber/500",
+ "amber-4": "darkMode/amber/400",
"blue-1": "neutral/white",
- "blue-2": "darkMode/blue/400",
- "blue-3": "darkMode/blue/300",
- "cyan-1": "darkMode/cyan/300",
- "pink-1": "darkMode/pink/300",
- "violet-1": "darkMode/violet/300",
- "blue-link": "darkMode/blue/500"
+ "blue-2": "darkMode/blue/700",
+ "blue-3": "darkMode/blue/400",
+ "blue-4": "darkMode/blue/300",
+ "cyan-3": "darkMode/cyan/400",
+ "violet-1": "neutral/white",
+ "violet-2": "darkMode/violet/700",
+ "violet-3": "darkMode/violet/400",
+ "violet-4": "darkMode/violet/300",
+ "blue-link": "darkMode/blue/500",
+ "alert-button-default": "darkMode/gray/50",
+ "alert-button-info": "darkMode/blue/200",
+ "alert-button-success": "darkMode/green/200",
+ "alert-button-warning": "darkMode/amber/200",
+ "alert-button-error": "darkMode/red/200",
+ "white": "darkMode/gray/900",
+ "gray-9": "darkMode/gray/50",
+ "cyan-1": "darkMode/cyan/500",
+ "pink-1": "darkMode/pink/500"
+ },
+ "outline": {
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-1-contrast": "darkMode/gray/700",
+ "gray-2": "darkMode/gray/600",
+ "gray-3": "darkMode/gray/500",
+ "gray-4": "darkMode/gray/450",
+ "gray-5": "lightMode/gray/200",
+ "red-2": "darkMode/red/800",
+ "red-3": "darkMode/red/700",
+ "red-4": "darkMode/red/600",
+ "green-2": "darkMode/green/800",
+ "green-3": "darkMode/green/700",
+ "green-4": "darkMode/green/600",
+ "amber-2": "darkMode/amber/800",
+ "amber-3": "darkMode/amber/700",
+ "amber-4": "darkMode/amber/600",
+ "blue-2": "darkMode/blue/800",
+ "blue-3": "darkMode/blue/700",
+ "blue-4": "darkMode/blue/600",
+ "orange-3": "darkMode/orange/700",
+ "violet-2": "darkMode/violet/800",
+ "violet-3": "darkMode/violet/700",
+ "violet-4": "darkMode/violet/600",
+ "gray-modal": "darkMode/gray/600",
+ "gray-modals": "darkMode/gray/600",
+ "white": "darkMode/gray/900",
+ "blue-1": "darkMode/blue/800",
+ "red-1": "darkMode/red/800",
+ "green-1": "darkMode/green/800",
+ "amber-1": "darkMode/amber/800",
+ "orange-1": "darkMode/orange/800"
}
}
}
diff --git a/tailwind/figma-tokens-to-theme.js b/tailwind/figma-tokens-to-theme.js
new file mode 100644
index 000000000..57dd17124
--- /dev/null
+++ b/tailwind/figma-tokens-to-theme.js
@@ -0,0 +1,400 @@
+/**
+ * Generator: reads the W3C Design Tokens Community Group JSON exported from
+ * Figma (espresso-v2-design-tokens/) and emits theme JSON files that the
+ * tailwind plugin can consume.
+ *
+ * Inputs: espresso-v2-design-tokens/*.tokens.json
+ * Outputs: tailwind/generated/{colors,radius,typography}.json
+ *
+ * Run with: yarn sync-tokens
+ */
+
+import fs from 'fs'
+import path from 'path'
+import { fileURLToPath } from 'url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const REPO_ROOT = path.resolve(__dirname, '..')
+const TOKENS_DIR = path.join(REPO_ROOT, 'espresso-v2-design-tokens')
+const OUT_DIR = path.join(__dirname, 'generated')
+
+// Color families mirrored from Figma's "🔵 Colour primitives" collection.
+// Each appears under `light.` and `dark.` plus their alpha pair.
+const COLOR_FAMILIES = [
+ 'gray',
+ 'blue',
+ 'green',
+ 'red',
+ 'orange',
+ 'amber',
+ 'yellow',
+ 'teal',
+ 'cyan',
+ 'purple',
+ 'pink',
+ 'violet',
+]
+const ALPHA_FAMILIES = ['gray-alpha', 'red-alpha']
+
+// Named aliases layered on top of Figma's numeric radius keys.
+// Each name is matched by px value, so the alias stays correct if Figma shifts.
+const RADIUS_NAME_BY_PX = {
+ '0px': 'none',
+ '4px': 'sm',
+ '8px': 'DEFAULT',
+ '10px': 'md',
+ '12px': 'lg',
+ '16px': 'xl',
+ '20px': '2xl',
+}
+// Preserved from current plugin.js — Figma doesn't model `full`.
+const RADIUS_EXTRA = { full: '9999px' }
+
+// Figma's font weight strings → tailwind numeric weights.
+const FONT_WEIGHT_MAP = {
+ regular: 400,
+ medium: 500,
+ semibold: 600,
+ bold: 700,
+ extrabold: 800,
+}
+
+function readTokens(filename) {
+ return JSON.parse(fs.readFileSync(path.join(TOKENS_DIR, filename), 'utf8'))
+}
+
+function ensureOutDir() {
+ fs.mkdirSync(OUT_DIR, { recursive: true })
+}
+
+function writeJSON(filename, data) {
+ const filepath = path.join(OUT_DIR, filename)
+ fs.writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n')
+ console.log(` wrote ${path.relative(REPO_ROOT, filepath)}`)
+}
+
+// ---------- COLORS ----------
+
+// Build colors.json in the shape colorPalette.js already consumes:
+// { lightMode, darkMode, overlay, neutral, themedVariables: { light, dark } }
+function buildColors() {
+ const primitives = readTokens('🔵 Colour primitives.Light.tokens.json')
+ const stylesLight = readTokens('Styles.Light.tokens.json')
+ const stylesDark = readTokens('Styles.Dark.tokens.json')
+
+ const colors = {
+ lightMode: {},
+ darkMode: {},
+ overlay: { white: {}, black: {} },
+ neutral: {
+ white: primitives.neutral.white.$value,
+ black: primitives.neutral.black.$value,
+ },
+ themedVariables: {
+ light: { surface: {}, ink: {}, outline: {} },
+ dark: { surface: {}, ink: {}, outline: {} },
+ },
+ }
+
+ // Primitive ramps — light..
+ for (const family of [...COLOR_FAMILIES, ...ALPHA_FAMILIES]) {
+ if (primitives.light?.[family]) {
+ colors.lightMode[family] = mapShades(primitives.light[family])
+ }
+ if (primitives.dark?.[family]) {
+ colors.darkMode[family] = mapShades(primitives.dark[family])
+ }
+ }
+
+ // Overlay ramps — white-alpha / black-alpha at top level of primitives.
+ if (primitives['white-alpha']) {
+ colors.overlay.white = mapShades(primitives['white-alpha'])
+ }
+ if (primitives['black-alpha']) {
+ colors.overlay.black = mapShades(primitives['black-alpha'])
+ }
+
+ // Semantic aliases — Styles.Light/Dark → themedVariables.{light,dark}
+ collectSemanticCategory(stylesLight, 'surface', colors.themedVariables.light)
+ collectSemanticCategory(stylesLight, 'ink', colors.themedVariables.light)
+ collectSemanticCategory(stylesLight, 'outline', colors.themedVariables.light)
+ collectSemanticCategory(stylesDark, 'surface', colors.themedVariables.dark)
+ collectSemanticCategory(stylesDark, 'ink', colors.themedVariables.dark)
+ collectSemanticCategory(stylesDark, 'outline', colors.themedVariables.dark)
+
+ applyAliases(colors.themedVariables.light)
+ applyAliases(colors.themedVariables.dark)
+ applyLegacyEntries(colors.themedVariables.light, LEGACY_ENTRIES.light)
+ applyLegacyEntries(colors.themedVariables.dark, LEGACY_ENTRIES.dark)
+
+ return colors
+}
+
+// Renames — legacy name points at the current Figma name in the same category.
+// Resolves dynamically so the legacy entry tracks any future value change.
+const ALIASES = {
+ outline: {
+ 'gray-modals': 'gray-modal',
+ },
+}
+
+// Legacy semantic tokens with no surviving Figma equivalent — keep them pinned
+// to a primitive reference so src/ consumers keep working. Values use the
+// colorsData reference shape ("lightMode/gray/900", "neutral/white") that
+// colorPalette.js#resolveColorReference already understands. Only added when
+// the Figma export doesn't already define the name.
+const LEGACY_ENTRIES = {
+ light: {
+ surface: {
+ white: 'neutral/white',
+ modal: 'neutral/white',
+ selected: 'neutral/white',
+ cards: 'neutral/white',
+ 'cyan-1': 'lightMode/cyan/100',
+ 'pink-1': 'lightMode/pink/100',
+ 'orange-1': 'lightMode/orange/100',
+ 'violet-1': 'lightMode/violet/100',
+ 'green-3': 'lightMode/green/600',
+ 'amber-3': 'lightMode/amber/600',
+ 'blue-3': 'lightMode/blue/600',
+ },
+ ink: {
+ white: 'neutral/white',
+ 'gray-9': 'lightMode/gray/900',
+ 'cyan-1': 'lightMode/cyan/500',
+ 'pink-1': 'lightMode/pink/500',
+ 'violet-1': 'lightMode/violet/500',
+ },
+ outline: {
+ white: 'neutral/white',
+ 'blue-1': 'lightMode/blue/300',
+ 'red-1': 'lightMode/red/300',
+ 'green-1': 'lightMode/green/200',
+ 'amber-1': 'lightMode/amber/200',
+ 'orange-1': 'lightMode/orange/200',
+ },
+ },
+ dark: {
+ surface: {
+ white: 'darkMode/gray/900',
+ modal: 'darkMode/gray/700',
+ selected: 'darkMode/gray/500',
+ cards: 'darkMode/gray/800',
+ 'cyan-1': 'darkMode/cyan/900',
+ 'pink-1': 'darkMode/pink/900',
+ 'orange-1': 'darkMode/orange/900',
+ 'violet-1': 'darkMode/violet/900',
+ 'green-3': 'darkMode/green/400',
+ 'amber-3': 'darkMode/amber/400',
+ 'blue-3': 'darkMode/blue/400',
+ },
+ ink: {
+ white: 'darkMode/gray/900',
+ 'gray-9': 'darkMode/gray/50',
+ 'cyan-1': 'darkMode/cyan/500',
+ 'pink-1': 'darkMode/pink/500',
+ 'violet-1': 'darkMode/violet/500',
+ },
+ outline: {
+ white: 'darkMode/gray/900',
+ 'blue-1': 'darkMode/blue/800',
+ 'red-1': 'darkMode/red/800',
+ 'green-1': 'darkMode/green/800',
+ 'amber-1': 'darkMode/amber/800',
+ 'orange-1': 'darkMode/orange/800',
+ },
+ },
+}
+
+function applyAliases(themed) {
+ for (const [category, map] of Object.entries(ALIASES)) {
+ if (!themed[category]) continue
+ for (const [legacy, current] of Object.entries(map)) {
+ if (themed[category][current] && !themed[category][legacy]) {
+ themed[category][legacy] = themed[category][current]
+ }
+ }
+ }
+}
+
+function applyLegacyEntries(themed, legacy) {
+ for (const [category, entries] of Object.entries(legacy)) {
+ themed[category] = themed[category] || {}
+ for (const [name, ref] of Object.entries(entries)) {
+ if (!themed[category][name]) {
+ themed[category][name] = ref
+ }
+ }
+ }
+}
+
+function mapShades(family) {
+ const out = {}
+ for (const [shade, token] of Object.entries(family)) {
+ if (token && token.$value) {
+ out[shade] = token.$value
+ }
+ }
+ return out
+}
+
+function collectSemanticCategory(styles, category, target) {
+ const section = styles[category]
+ if (!section) return
+ target[category] = target[category] || {}
+ for (const [name, token] of Object.entries(section)) {
+ if (!token?.$value) continue
+ // Resolve `{path.to.token}` aliases into the "lightMode/family/shade" format
+ // that colorPalette.js#resolveColorReference understands.
+ target[category][name] = aliasToReference(token.$value)
+ }
+}
+
+// Convert a DTCG alias string like "{light.gray.50}" into the reference shape
+// stored in colors.json today: "lightMode/gray/50". Non-aliases (literal hex)
+// pass through unchanged so the consumer can decide whether to treat them as
+// resolved values.
+function aliasToReference(value) {
+ if (typeof value !== 'string') return value
+ const match = value.match(/^\{(.+)\}$/)
+ if (!match) return value
+ const segments = match[1].split('.')
+
+ // {neutral.white} | {neutral.black}
+ if (segments[0] === 'neutral' && segments.length === 2) {
+ return `neutral/${segments[1]}`
+ }
+ // {white-alpha.50} | {black-alpha.50} → overlay/white/50 | overlay/black/50
+ if (segments[0] === 'white-alpha' || segments[0] === 'black-alpha') {
+ const color = segments[0].split('-')[0]
+ return `overlay/${color}/${segments[1]}`
+ }
+ // {light.gray.50} → lightMode/gray/50
+ // {light.gray-alpha.50} → lightMode/gray-alpha/50
+ if (segments[0] === 'light') {
+ return `lightMode/${segments.slice(1, -1).join('-')}/${segments[segments.length - 1]}`
+ }
+ if (segments[0] === 'dark') {
+ return `darkMode/${segments.slice(1, -1).join('-')}/${segments[segments.length - 1]}`
+ }
+ console.warn(` ⚠ unresolved alias: ${value}`)
+ return value
+}
+
+// ---------- RADIUS ----------
+
+function buildRadius() {
+ const tokens = readTokens('Tokens.Mode 1.tokens.json')
+ const radius = { ...RADIUS_EXTRA }
+
+ for (const [key, token] of Object.entries(tokens.radius || {})) {
+ const px = token.$value
+ radius[key] = px
+ const name = RADIUS_NAME_BY_PX[px]
+ if (name) radius[name] = px
+ }
+ return radius
+}
+
+// ---------- TYPOGRAPHY ----------
+
+function buildTypography() {
+ const tokens = readTokens('Typography.Desktop.tokens.json')
+ const sizes = tokens.font?.size || {}
+ const lineHeights = tokens.font?.['line-height'] || {}
+ const weights = tokens.font?.weight || {}
+ const families = tokens.font?.family || {}
+
+ const fontSize = {}
+ for (const [key, token] of Object.entries(sizes)) {
+ const lh = lineHeights[key]?.$value
+ fontSize[key] = [
+ token.$value,
+ {
+ lineHeight: lh || '1.15',
+ },
+ ]
+ }
+
+ const fontWeight = {}
+ for (const [key, token] of Object.entries(weights)) {
+ const numeric = FONT_WEIGHT_MAP[token.$value]
+ fontWeight[key] = numeric ?? token.$value
+ }
+
+ const fontFamily = {}
+ for (const [key, token] of Object.entries(families)) {
+ fontFamily[key] = token.$value
+ }
+
+ return { fontFamily, fontSize, fontWeight }
+}
+
+// ---------- EFFECTS (shadows) ----------
+
+// Figma exports shadow effects as DTCG `$type: shadow` tokens — an array of
+// layers, each with offsetX/offsetY/blur/spread/color (+ optional inset).
+// Emit pre-composed CSS box-shadow strings so the plugin can drop them into
+// CSS variables verbatim.
+function buildEffects() {
+ const tokens = readTokens('effect.styles.tokens.json')
+ const out = {
+ elevation: { light: {}, dark: {}, custom: {} },
+ focus: { light: {}, dark: {} },
+ }
+
+ for (const step of Object.keys(tokens.elevation?.light || {})) {
+ out.elevation.light[step] = shadowToCss(tokens.elevation.light[step].$value)
+ }
+ for (const step of Object.keys(tokens.elevation?.dark || {})) {
+ out.elevation.dark[step] = shadowToCss(tokens.elevation.dark[step].$value)
+ }
+ for (const [name, token] of Object.entries(tokens.elevation?.custom || {})) {
+ out.elevation.custom[name] = shadowToCss(token.$value)
+ }
+ for (const [name, token] of Object.entries(tokens.focus?.light || {})) {
+ out.focus.light[name] = shadowToCss(token.$value)
+ }
+ for (const [name, token] of Object.entries(tokens.focus?.dark || {})) {
+ out.focus.dark[name] = shadowToCss(token.$value)
+ }
+
+ return out
+}
+
+function shadowToCss(layers) {
+ return layers
+ .map((layer) => {
+ const parts = [
+ layer.inset ? 'inset' : null,
+ layer.offsetX,
+ layer.offsetY,
+ layer.blur,
+ layer.spread || '0px',
+ layer.color,
+ ].filter(Boolean)
+ return parts.join(' ')
+ })
+ .join(', ')
+}
+
+// ---------- MAIN ----------
+
+function main() {
+ if (!fs.existsSync(TOKENS_DIR)) {
+ console.error(`✗ tokens directory not found: ${TOKENS_DIR}`)
+ process.exit(1)
+ }
+
+ console.log(`Reading tokens from ${path.relative(REPO_ROOT, TOKENS_DIR)}/`)
+ ensureOutDir()
+
+ writeJSON('colors.json', buildColors())
+ writeJSON('radius.json', buildRadius())
+ writeJSON('typography.json', buildTypography())
+ writeJSON('effects.json', buildEffects())
+
+ console.log('✓ done')
+}
+
+main()
diff --git a/tailwind/generated/colors.json b/tailwind/generated/colors.json
new file mode 100644
index 000000000..c8c9885f9
--- /dev/null
+++ b/tailwind/generated/colors.json
@@ -0,0 +1,675 @@
+{
+ "lightMode": {
+ "gray": {
+ "50": "#f8f8f8",
+ "100": "#f3f3f3",
+ "200": "#ededed",
+ "300": "#e2e2e2",
+ "400": "#c7c7c7",
+ "500": "#999999",
+ "600": "#7c7c7c",
+ "700": "#525252",
+ "800": "#383838",
+ "900": "#171717",
+ "950": "#0f0f0f"
+ },
+ "blue": {
+ "50": "#f0f7fc",
+ "100": "#e6f4ff",
+ "200": "#c8e6ff",
+ "300": "#a7d7fd",
+ "400": "#73bbf6",
+ "500": "#0289f7",
+ "600": "#007be0",
+ "700": "#0070cc",
+ "800": "#005ca3",
+ "900": "#004880",
+ "950": "#032e63"
+ },
+ "green": {
+ "50": "#f1fbf5",
+ "100": "#e4faeb",
+ "200": "#c3f9d3",
+ "300": "#a6efc0",
+ "400": "#78e09f",
+ "500": "#46b37e",
+ "600": "#268c5c",
+ "700": "#137949",
+ "800": "#075e35",
+ "900": "#173b2c",
+ "950": "#0a3020"
+ },
+ "red": {
+ "50": "#fff7f7",
+ "100": "#ffe7e7",
+ "200": "#ffd8d8",
+ "300": "#fdc2c2",
+ "400": "#f79596",
+ "500": "#e03636",
+ "600": "#cc2929",
+ "700": "#b52a2a",
+ "800": "#941f1f",
+ "900": "#6b1515",
+ "950": "#4c0d0d"
+ },
+ "orange": {
+ "50": "#fff4ed",
+ "100": "#ffefe4",
+ "200": "#ffdec5",
+ "300": "#ffcba3",
+ "400": "#f4b07f",
+ "500": "#e86c13",
+ "600": "#d35a09",
+ "700": "#bd3e0c",
+ "800": "#9e3513",
+ "900": "#6b2711",
+ "950": "#491605"
+ },
+ "amber": {
+ "50": "#fdfaed",
+ "100": "#fff7d3",
+ "200": "#feeda9",
+ "300": "#fbdb73",
+ "400": "#fbc53f",
+ "500": "#e79913",
+ "600": "#d47408",
+ "700": "#b35309",
+ "800": "#91400d",
+ "900": "#763813",
+ "950": "#4e2209"
+ },
+ "yellow": {
+ "50": "#fcfbe8",
+ "100": "#fff7d3",
+ "200": "#f7e9a8",
+ "300": "#f5e171",
+ "400": "#f2d14b",
+ "500": "#edba13",
+ "600": "#ab790d",
+ "700": "#9a6304",
+ "800": "#8c5600",
+ "900": "#733f12",
+ "950": "#512a09"
+ },
+ "teal": {
+ "50": "#eefbf8",
+ "100": "#e6f7f4",
+ "200": "#bae8e1",
+ "300": "#97ded4",
+ "400": "#73d1c4",
+ "500": "#36baad",
+ "600": "#0a857b",
+ "700": "#0f736b",
+ "800": "#115c57",
+ "900": "#114541",
+ "950": "#053734"
+ },
+ "cyan": {
+ "50": "#f2fbfd",
+ "100": "#ddf7ff",
+ "200": "#b3e8f7",
+ "300": "#99e2f8",
+ "400": "#72d5f3",
+ "500": "#3bbde5",
+ "600": "#1f8cad",
+ "700": "#1d7f9d",
+ "800": "#125c73",
+ "900": "#164759",
+ "950": "#05383f"
+ },
+ "purple": {
+ "50": "#f8f3fb",
+ "100": "#f6e9ff",
+ "200": "#ecd3ff",
+ "300": "#e2b9fc",
+ "400": "#bf78fa",
+ "500": "#9c45e3",
+ "600": "#8e49ca",
+ "700": "#6e399d",
+ "800": "#5c2f83",
+ "900": "#401863",
+ "950": "#2d084e"
+ },
+ "pink": {
+ "50": "#fff7fc",
+ "100": "#fde8f5",
+ "200": "#ffd5f0",
+ "300": "#f9b9e0",
+ "400": "#f77cc6",
+ "500": "#e34aa6",
+ "600": "#cf3a96",
+ "700": "#9c2671",
+ "800": "#801458",
+ "900": "#570f3e",
+ "950": "#40062c"
+ },
+ "violet": {
+ "50": "#f5f3fc",
+ "100": "#eee8ff",
+ "200": "#dbd5ff",
+ "300": "#c9bafb",
+ "400": "#a68efe",
+ "500": "#7757ee",
+ "600": "#6b53d0",
+ "700": "#4f3da1",
+ "800": "#392980",
+ "900": "#251959",
+ "950": "#150655"
+ },
+ "gray-alpha": {
+ "50": "#00000008",
+ "100": "#0000000b",
+ "200": "#00000012",
+ "300": "#0000001d",
+ "400": "#00000038",
+ "500": "#00000066",
+ "600": "#00000083",
+ "700": "#000000ad",
+ "800": "#000000c7",
+ "900": "#000000e8",
+ "950": "#000000f0"
+ }
+ },
+ "darkMode": {
+ "gray": {
+ "50": "#f8f8f8",
+ "100": "#d9d9d9",
+ "200": "#afafaf",
+ "300": "#808080",
+ "400": "#717171",
+ "450": "#575757",
+ "500": "#424242",
+ "600": "#343434",
+ "700": "#2b2b2b",
+ "800": "#242424",
+ "900": "#1f1f1f",
+ "950": "#171717"
+ },
+ "blue": {
+ "50": "#c9e0f5",
+ "100": "#add2f5",
+ "200": "#8cc1ec",
+ "300": "#5aaef2",
+ "400": "#2a8edf",
+ "500": "#1580d8",
+ "600": "#155999",
+ "700": "#063d71",
+ "800": "#052b53",
+ "900": "#10233d",
+ "950": "#0e1c2f"
+ },
+ "green": {
+ "50": "#c8f3de",
+ "100": "#9be6c1",
+ "200": "#78d7a9",
+ "300": "#58c08e",
+ "400": "#469170",
+ "500": "#148950",
+ "600": "#077241",
+ "700": "#035831",
+ "800": "#0a3f27",
+ "900": "#0c2d1c",
+ "950": "#0b1e14"
+ },
+ "red": {
+ "50": "#ffdede",
+ "100": "#ffc1c1",
+ "200": "#fe7c7c",
+ "300": "#eb4d52",
+ "400": "#ce5a5a",
+ "500": "#b01f1f",
+ "600": "#862020",
+ "700": "#661717",
+ "800": "#521515",
+ "900": "#441316",
+ "950": "#271111"
+ },
+ "orange": {
+ "50": "#ffcdad",
+ "100": "#ffa873",
+ "200": "#fa8a40",
+ "300": "#e3701c",
+ "400": "#e16914",
+ "500": "#984509",
+ "600": "#823906",
+ "700": "#683108",
+ "800": "#532707",
+ "900": "#361c09",
+ "950": "#2b1708"
+ },
+ "amber": {
+ "50": "#ffe59a",
+ "100": "#f4c25f",
+ "200": "#ffaa3e",
+ "300": "#fa961f",
+ "400": "#c87a2d",
+ "500": "#bd660f",
+ "600": "#975215",
+ "700": "#753a07",
+ "800": "#4b2606",
+ "900": "#371e06",
+ "950": "#281808"
+ },
+ "yellow": {
+ "50": "#ffeeb8",
+ "100": "#ffe386",
+ "200": "#f8d76a",
+ "300": "#ecc02e",
+ "400": "#bb972a",
+ "500": "#9c7e1c",
+ "600": "#99780a",
+ "700": "#705606",
+ "800": "#5b4605",
+ "900": "#3a2c04",
+ "950": "#261d03"
+ },
+ "teal": {
+ "50": "#a0f7ed",
+ "100": "#7ef3e7",
+ "200": "#51decf",
+ "300": "#28bcac",
+ "400": "#2ca094",
+ "500": "#1b7169",
+ "600": "#145b54",
+ "700": "#0b4942",
+ "800": "#0b3a35",
+ "900": "#0a2d29",
+ "950": "#0b2320"
+ },
+ "cyan": {
+ "50": "#d0f0fa",
+ "100": "#95e3f6",
+ "200": "#62cae9",
+ "300": "#3cb8dc",
+ "400": "#249cc2",
+ "500": "#107b9b",
+ "600": "#0c6783",
+ "700": "#104f62",
+ "800": "#0d3b49",
+ "900": "#0b2932",
+ "950": "#0b2028"
+ },
+ "purple": {
+ "50": "#e5c6fb",
+ "100": "#d9aff5",
+ "200": "#c993ef",
+ "300": "#b168e8",
+ "400": "#a26fce",
+ "500": "#7a2db9",
+ "600": "#622195",
+ "700": "#491870",
+ "800": "#391457",
+ "900": "#2c1042",
+ "950": "#23132f"
+ },
+ "pink": {
+ "50": "#ffbbe4",
+ "100": "#f69ad1",
+ "200": "#ed77be",
+ "300": "#e359ab",
+ "400": "#cb5d9e",
+ "500": "#ac377d",
+ "600": "#892660",
+ "700": "#6f1d4d",
+ "800": "#5b183f",
+ "900": "#42132f",
+ "950": "#2e0f22"
+ },
+ "violet": {
+ "50": "#cdbeff",
+ "100": "#bca9fc",
+ "200": "#9f87ed",
+ "300": "#9478f8",
+ "400": "#9683d8",
+ "500": "#785fce",
+ "600": "#403397",
+ "700": "#3d3286",
+ "800": "#291d64",
+ "900": "#1f1841",
+ "950": "#18142e"
+ },
+ "gray-alpha": {
+ "50": "#fffffff7",
+ "100": "#ffffffd1",
+ "200": "#ffffffab",
+ "300": "#ffffff78",
+ "400": "#ffffff69",
+ "450": "#ffffff4d",
+ "500": "#ffffff36",
+ "600": "#ffffff26",
+ "700": "#ffffff1f",
+ "800": "#ffffff14",
+ "900": "#ffffff0f",
+ "950": "#0000000a"
+ },
+ "red-alpha": {
+ "50": "#ffdede",
+ "100": "#ffc1c1",
+ "200": "#fe7c7c",
+ "300": "#ff5858f0",
+ "400": "#fa3c3cd9",
+ "500": "#ed2222ba",
+ "600": "#ed2d2d8a",
+ "700": "#c120207d",
+ "800": "#c01b1b61",
+ "900": "#5f16168f",
+ "950": "#53060666"
+ }
+ },
+ "overlay": {
+ "white": {
+ "50": "#ffffff1a",
+ "100": "#ffffff2e",
+ "200": "#ffffff45",
+ "300": "#ffffff5c",
+ "400": "#ffffff73",
+ "500": "#ffffff8a",
+ "600": "#ffffffa1",
+ "700": "#ffffffb8",
+ "800": "#ffffffcf",
+ "900": "#ffffffe5",
+ "950": "#fffffff2"
+ },
+ "black": {
+ "50": "#00000017",
+ "100": "#0000002e",
+ "200": "#00000045",
+ "300": "#0000005c",
+ "400": "#00000073",
+ "500": "#0000008a",
+ "600": "#000000a1",
+ "700": "#000000b8",
+ "800": "#000000cf",
+ "900": "#000000e5",
+ "950": "#000000f2"
+ }
+ },
+ "neutral": {
+ "white": "#ffffff",
+ "black": "#000000"
+ },
+ "themedVariables": {
+ "light": {
+ "surface": {
+ "base": "neutral/white",
+ "gray-1": "lightMode/gray/50",
+ "gray-2": "lightMode/gray/100",
+ "gray-3": "lightMode/gray/200",
+ "gray-4": "lightMode/gray/300",
+ "gray-5": "lightMode/gray/700",
+ "gray-6": "lightMode/gray/800",
+ "gray-7": "lightMode/gray/900",
+ "menu-bar": "lightMode/gray/50",
+ "base-contrast": "neutral/white",
+ "gray-1-contrast": "neutral/white",
+ "gray-2-contrast": "neutral/white",
+ "red-1": "lightMode/red/50",
+ "red-2": "lightMode/red/100",
+ "red-3": "lightMode/red/200",
+ "red-4": "lightMode/red/300",
+ "red-5": "lightMode/red/600",
+ "red-6": "lightMode/red/700",
+ "red-7": "lightMode/red/800",
+ "green-1": "lightMode/green/50",
+ "green-2": "lightMode/green/100",
+ "green-3": "lightMode/green/200",
+ "green-4": "lightMode/green/300",
+ "green-5": "lightMode/green/600",
+ "green-6": "lightMode/green/700",
+ "green-7": "lightMode/green/800",
+ "amber-1": "lightMode/amber/50",
+ "amber-2": "lightMode/amber/100",
+ "amber-3": "lightMode/amber/200",
+ "amber-4": "lightMode/amber/300",
+ "amber-5": "lightMode/amber/600",
+ "amber-6": "lightMode/amber/700",
+ "amber-7": "lightMode/amber/800",
+ "blue-1": "lightMode/blue/50",
+ "blue-2": "lightMode/blue/100",
+ "blue-3": "lightMode/blue/200",
+ "blue-4": "lightMode/blue/300",
+ "blue-5": "lightMode/blue/600",
+ "blue-6": "lightMode/blue/700",
+ "blue-7": "lightMode/blue/800",
+ "orange-2": "lightMode/orange/100",
+ "violet-2": "lightMode/violet/100",
+ "violet-3": "lightMode/violet/200",
+ "violet-4": "lightMode/violet/300",
+ "violet-5": "lightMode/violet/600",
+ "violet-6": "lightMode/violet/700",
+ "violet-7": "lightMode/violet/800",
+ "cyan-2": "lightMode/cyan/100",
+ "alert-button-default": "neutral/white",
+ "alert-button-info": "neutral/white",
+ "alert-button-success": "neutral/white",
+ "alert-button-warning": "neutral/white",
+ "alert-button-error": "neutral/white",
+ "white": "neutral/white",
+ "modal": "neutral/white",
+ "selected": "neutral/white",
+ "cards": "neutral/white",
+ "cyan-1": "lightMode/cyan/100",
+ "pink-1": "lightMode/pink/100",
+ "orange-1": "lightMode/orange/100",
+ "violet-1": "lightMode/violet/100"
+ },
+ "ink": {
+ "base": "neutral/white",
+ "gray-1": "lightMode/gray/200",
+ "gray-2": "lightMode/gray/300",
+ "gray-3": "lightMode/gray/400",
+ "gray-4": "lightMode/gray/500",
+ "gray-5": "lightMode/gray/600",
+ "gray-6": "lightMode/gray/700",
+ "gray-7": "lightMode/gray/800",
+ "gray-8": "lightMode/gray/900",
+ "red-1": "lightMode/red/50",
+ "red-2": "lightMode/red/400",
+ "red-3": "lightMode/red/500",
+ "red-4": "lightMode/red/700",
+ "green-1": "lightMode/green/50",
+ "green-2": "lightMode/green/400",
+ "green-3": "lightMode/green/500",
+ "green-4": "lightMode/green/700",
+ "green-6": "lightMode/green/600",
+ "amber-1": "lightMode/amber/50",
+ "amber-2": "lightMode/amber/400",
+ "amber-3": "lightMode/amber/500",
+ "amber-4": "lightMode/amber/700",
+ "blue-1": "lightMode/blue/50",
+ "blue-2": "lightMode/blue/400",
+ "blue-3": "lightMode/blue/500",
+ "blue-4": "lightMode/blue/700",
+ "cyan-3": "lightMode/cyan/500",
+ "violet-1": "lightMode/violet/50",
+ "violet-2": "lightMode/violet/400",
+ "violet-3": "lightMode/violet/500",
+ "violet-4": "lightMode/violet/700",
+ "blue-link": "lightMode/blue/400",
+ "alert-button-default": "lightMode/gray/900",
+ "alert-button-info": "lightMode/gray/900",
+ "alert-button-success": "lightMode/gray/900",
+ "alert-button-warning": "lightMode/gray/900",
+ "alert-button-error": "lightMode/gray/900",
+ "white": "neutral/white",
+ "gray-9": "lightMode/gray/900",
+ "cyan-1": "lightMode/cyan/500",
+ "pink-1": "lightMode/pink/500"
+ },
+ "outline": {
+ "base": "neutral/white",
+ "gray-1": "lightMode/gray/200",
+ "gray-1-contrast": "lightMode/gray/200",
+ "gray-2": "lightMode/gray/300",
+ "gray-3": "lightMode/gray/400",
+ "gray-4": "lightMode/gray/500",
+ "gray-5": "lightMode/gray/800",
+ "red-2": "lightMode/red/300",
+ "red-3": "lightMode/red/400",
+ "red-4": "lightMode/red/500",
+ "green-2": "lightMode/green/300",
+ "green-3": "lightMode/green/400",
+ "green-4": "lightMode/green/500",
+ "amber-2": "lightMode/amber/300",
+ "amber-3": "lightMode/amber/400",
+ "amber-4": "lightMode/amber/500",
+ "blue-2": "lightMode/blue/300",
+ "blue-3": "lightMode/blue/400",
+ "blue-4": "lightMode/blue/500",
+ "orange-3": "lightMode/orange/400",
+ "violet-2": "lightMode/violet/300",
+ "violet-3": "lightMode/violet/400",
+ "violet-4": "lightMode/violet/500",
+ "gray-modal": "lightMode/gray/200",
+ "gray-modals": "lightMode/gray/200",
+ "white": "neutral/white",
+ "blue-1": "lightMode/blue/300",
+ "red-1": "lightMode/red/300",
+ "green-1": "lightMode/green/200",
+ "amber-1": "lightMode/amber/200",
+ "orange-1": "lightMode/orange/200"
+ }
+ },
+ "dark": {
+ "surface": {
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-2": "darkMode/gray/700",
+ "gray-3": "darkMode/gray/600",
+ "gray-4": "darkMode/gray/500",
+ "gray-5": "darkMode/gray/200",
+ "gray-6": "darkMode/gray/100",
+ "gray-7": "darkMode/gray/50",
+ "menu-bar": "darkMode/gray/950",
+ "base-contrast": "darkMode/gray/900",
+ "gray-1-contrast": "darkMode/gray/900",
+ "gray-2-contrast": "darkMode/gray/600",
+ "red-1": "darkMode/red/950",
+ "red-2": "darkMode/red/900",
+ "red-3": "darkMode/red/800",
+ "red-4": "darkMode/red/700",
+ "red-5": "darkMode/red/500",
+ "red-6": "darkMode/red/400",
+ "red-7": "darkMode/red/600",
+ "green-1": "darkMode/green/900",
+ "green-2": "darkMode/green/900",
+ "green-3": "darkMode/green/800",
+ "green-4": "darkMode/green/700",
+ "green-5": "darkMode/green/500",
+ "green-6": "darkMode/green/400",
+ "green-7": "darkMode/green/600",
+ "amber-1": "darkMode/amber/950",
+ "amber-2": "darkMode/amber/900",
+ "amber-3": "darkMode/amber/800",
+ "amber-4": "darkMode/amber/700",
+ "amber-5": "darkMode/amber/500",
+ "amber-6": "darkMode/amber/400",
+ "amber-7": "darkMode/amber/600",
+ "blue-1": "darkMode/blue/950",
+ "blue-2": "darkMode/blue/900",
+ "blue-3": "darkMode/blue/800",
+ "blue-4": "darkMode/blue/700",
+ "blue-5": "darkMode/blue/500",
+ "blue-6": "darkMode/blue/400",
+ "blue-7": "darkMode/blue/600",
+ "orange-2": "darkMode/orange/900",
+ "violet-2": "darkMode/violet/900",
+ "violet-3": "darkMode/violet/800",
+ "violet-4": "darkMode/violet/700",
+ "violet-5": "darkMode/violet/500",
+ "violet-6": "darkMode/violet/400",
+ "violet-7": "darkMode/violet/600",
+ "cyan-2": "darkMode/cyan/900",
+ "alert-button-default": "darkMode/gray/500",
+ "alert-button-info": "darkMode/blue/700",
+ "alert-button-success": "darkMode/green/700",
+ "alert-button-warning": "darkMode/amber/700",
+ "alert-button-error": "darkMode/red/700",
+ "white": "darkMode/gray/900",
+ "modal": "darkMode/gray/700",
+ "selected": "darkMode/gray/500",
+ "cards": "darkMode/gray/800",
+ "cyan-1": "darkMode/cyan/900",
+ "pink-1": "darkMode/pink/900",
+ "orange-1": "darkMode/orange/900",
+ "violet-1": "darkMode/violet/900"
+ },
+ "ink": {
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-2": "darkMode/gray/600",
+ "gray-3": "darkMode/gray/500",
+ "gray-4": "darkMode/gray/450",
+ "gray-5": "darkMode/gray/400",
+ "gray-6": "darkMode/gray/300",
+ "gray-7": "darkMode/gray/200",
+ "gray-8": "darkMode/gray/100",
+ "red-1": "neutral/white",
+ "red-2": "darkMode/red/700",
+ "red-3": "darkMode/red/400",
+ "red-4": "darkMode/red/300",
+ "green-1": "neutral/white",
+ "green-2": "darkMode/green/700",
+ "green-3": "darkMode/green/400",
+ "green-4": "darkMode/green/300",
+ "green-6": "darkMode/green/400",
+ "amber-1": "neutral/white",
+ "amber-2": "darkMode/amber/700",
+ "amber-3": "darkMode/amber/500",
+ "amber-4": "darkMode/amber/400",
+ "blue-1": "neutral/white",
+ "blue-2": "darkMode/blue/700",
+ "blue-3": "darkMode/blue/400",
+ "blue-4": "darkMode/blue/300",
+ "cyan-3": "darkMode/cyan/400",
+ "violet-1": "neutral/white",
+ "violet-2": "darkMode/violet/700",
+ "violet-3": "darkMode/violet/400",
+ "violet-4": "darkMode/violet/300",
+ "blue-link": "darkMode/blue/500",
+ "alert-button-default": "darkMode/gray/50",
+ "alert-button-info": "darkMode/blue/200",
+ "alert-button-success": "darkMode/green/200",
+ "alert-button-warning": "darkMode/amber/200",
+ "alert-button-error": "darkMode/red/200",
+ "white": "darkMode/gray/900",
+ "gray-9": "darkMode/gray/50",
+ "cyan-1": "darkMode/cyan/500",
+ "pink-1": "darkMode/pink/500"
+ },
+ "outline": {
+ "base": "darkMode/gray/950",
+ "gray-1": "darkMode/gray/800",
+ "gray-1-contrast": "darkMode/gray/700",
+ "gray-2": "darkMode/gray/600",
+ "gray-3": "darkMode/gray/500",
+ "gray-4": "darkMode/gray/450",
+ "gray-5": "lightMode/gray/200",
+ "red-2": "darkMode/red/800",
+ "red-3": "darkMode/red/700",
+ "red-4": "darkMode/red/600",
+ "green-2": "darkMode/green/800",
+ "green-3": "darkMode/green/700",
+ "green-4": "darkMode/green/600",
+ "amber-2": "darkMode/amber/800",
+ "amber-3": "darkMode/amber/700",
+ "amber-4": "darkMode/amber/600",
+ "blue-2": "darkMode/blue/800",
+ "blue-3": "darkMode/blue/700",
+ "blue-4": "darkMode/blue/600",
+ "orange-3": "darkMode/orange/700",
+ "violet-2": "darkMode/violet/800",
+ "violet-3": "darkMode/violet/700",
+ "violet-4": "darkMode/violet/600",
+ "gray-modal": "darkMode/gray/600",
+ "gray-modals": "darkMode/gray/600",
+ "white": "darkMode/gray/900",
+ "blue-1": "darkMode/blue/800",
+ "red-1": "darkMode/red/800",
+ "green-1": "darkMode/green/800",
+ "amber-1": "darkMode/amber/800",
+ "orange-1": "darkMode/orange/800"
+ }
+ }
+ }
+}
diff --git a/tailwind/generated/effects.json b/tailwind/generated/effects.json
new file mode 100644
index 000000000..cfc3bcefa
--- /dev/null
+++ b/tailwind/generated/effects.json
@@ -0,0 +1,41 @@
+{
+ "elevation": {
+ "light": {
+ "sm": "0px 1px 3px 0px #00000024, 0px 0px 1px 0px #00000024, inset 0px 0.25px 1.5px 0px #ffffff14",
+ "base": "0px 2px 5px 0px #00000024, 0px 0px 1.5px 0px #00000029, inset 0px 0.25px 1.5px 0px #ffffff14",
+ "md": "0px 6px 12px -2px #0000001f, 0px 0px 6px 2px #00000008, 0px 0px 1.5px 0px #00000026, inset 0px 0.25px 1.5px 0px #ffffff14",
+ "lg": "0px 18px 22px -6px #0000001a, 0px 0px 6px 3px #00000008, 0px 0px 1.5px 0px #0000002e",
+ "xl": "0px 24px 30px -8px #0000001a, 0px 0px 10px 2px #0000000a, 0px 0px 1px 0px #00000033, inset 0px 0.25px 2px 0px #ffffff26",
+ "2xl": "0px 44px 52px -10px #0000001a, 0px 0px 10px 2px #00000008, 0px 0px 1.5px 0px #00000040, inset 0px 0.1px 2px 0px #ffffff14"
+ },
+ "dark": {
+ "sm": "0px 1px 3px 0px #000000b2, 0px 0px 14px 0px #0000002e, inset 0px 0.5px 0.5px 0.5px #ffffff08",
+ "base": "0px 2px 5px 0px #00000099, 0px 0px 14px 0px #0000002e, inset 0px 0.5px 0.5px 0.5px #ffffff08",
+ "md": "0px 6px 12px -2px #00000099, 0px 0px 16px 2px #00000033, inset 0px 0.5px 0.5px 0.5px #ffffff08",
+ "lg": "0px 18px 20px -8px #00000085, 0px 0px 16px 0px #0000001a, inset 0px 0.5px 1.5px 0.5px #ffffff0a",
+ "xl": "0px 26px 34px -6px #0000006b, 0px 0px 14px 2px #0000001f, inset 0px 0.5px 1.5px 0.5px #ffffff0a",
+ "2xl": "0px 44px 52px -4px #0000006b, 0px 0px 14px 10px #0000001f, inset 0px 0.5px 1.5px 0.5px #ffffff0f"
+ },
+ "custom": {
+ "status": "0px 0px 0px 1.5px #ffffff"
+ }
+ },
+ "focus": {
+ "light": {
+ "default": "0px 0px 0px 2px #c9c9c9e5",
+ "red": "0px 0px 0px 2px #fa9c9de5",
+ "green": "0px 0px 0px 2px #5ed29ce5",
+ "amber": "0px 0px 0px 2px #ffda7ce5",
+ "blue": "0px 0px 0px 2px #65b9fce5",
+ "violet": "0px 0px 0px 2px #bea2fce5"
+ },
+ "dark": {
+ "default": "0px 0px 0px 3px #464646cc",
+ "red": "0px 0px 0px 3px #751819cc",
+ "green": "0px 0px 0px 3px #1d563bcc",
+ "amber": "0px 0px 0px 3px #744811cc",
+ "blue": "0px 0px 0px 3px #0e3d62cc",
+ "violet": "0px 0px 0px 3px #412d87cc"
+ }
+ }
+}
diff --git a/tailwind/generated/radius.json b/tailwind/generated/radius.json
new file mode 100644
index 000000000..2e9a6ba5e
--- /dev/null
+++ b/tailwind/generated/radius.json
@@ -0,0 +1,20 @@
+{
+ "0": "0px",
+ "1": "4px",
+ "2": "5px",
+ "3": "6px",
+ "4": "8px",
+ "5": "10px",
+ "6": "12px",
+ "7": "16px",
+ "8": "20px",
+ "9": "100px",
+ "full": "9999px",
+ "none": "0px",
+ "sm": "4px",
+ "DEFAULT": "8px",
+ "md": "10px",
+ "lg": "12px",
+ "xl": "16px",
+ "2xl": "20px"
+}
diff --git a/tailwind/generated/typography.json b/tailwind/generated/typography.json
new file mode 100644
index 000000000..9d18f8cf6
--- /dev/null
+++ b/tailwind/generated/typography.json
@@ -0,0 +1,140 @@
+{
+ "fontFamily": {
+ "text": "Inter Variable"
+ },
+ "fontSize": {
+ "tiny": [
+ "11px",
+ {
+ "lineHeight": "0px"
+ }
+ ],
+ "2xs": [
+ "11px",
+ {
+ "lineHeight": "13px"
+ }
+ ],
+ "xs": [
+ "12px",
+ {
+ "lineHeight": "14px"
+ }
+ ],
+ "sm": [
+ "13px",
+ {
+ "lineHeight": "15px"
+ }
+ ],
+ "base": [
+ "14px",
+ {
+ "lineHeight": "16px"
+ }
+ ],
+ "lg": [
+ "16px",
+ {
+ "lineHeight": "18px"
+ }
+ ],
+ "xl": [
+ "18px",
+ {
+ "lineHeight": "21px"
+ }
+ ],
+ "2xl": [
+ "20px",
+ {
+ "lineHeight": "23px"
+ }
+ ],
+ "3xl": [
+ "24px",
+ {
+ "lineHeight": "28px"
+ }
+ ],
+ "4xl": [
+ "26px",
+ {
+ "lineHeight": "42px"
+ }
+ ],
+ "5xl": [
+ "28px",
+ {
+ "lineHeight": "45px"
+ }
+ ],
+ "6xl": [
+ "32px",
+ {
+ "lineHeight": "51px"
+ }
+ ],
+ "7xl": [
+ "40px",
+ {
+ "lineHeight": "56px"
+ }
+ ],
+ "8xl": [
+ "44px",
+ {
+ "lineHeight": "62px"
+ }
+ ],
+ "9xl": [
+ "48px",
+ {
+ "lineHeight": "67px"
+ }
+ ],
+ "10xl": [
+ "52px",
+ {
+ "lineHeight": "73px"
+ }
+ ],
+ "11xl": [
+ "56px",
+ {
+ "lineHeight": "78px"
+ }
+ ],
+ "12xl": [
+ "64px",
+ {
+ "lineHeight": "83px"
+ }
+ ],
+ "13xl": [
+ "72px",
+ {
+ "lineHeight": "92px"
+ }
+ ],
+ "14xl": [
+ "80px",
+ {
+ "lineHeight": "96px"
+ }
+ ],
+ "15xl": [
+ "88px",
+ {
+ "lineHeight": "106px"
+ }
+ ]
+ },
+ "fontWeight": {
+ "regular": 400,
+ "medium": 500,
+ "semibold": 600,
+ "bold": 700,
+ "black": 800
+ }
+}
diff --git a/tailwind/plugin.js b/tailwind/plugin.js
index e63f855ca..89ddd9beb 100644
--- a/tailwind/plugin.js
+++ b/tailwind/plugin.js
@@ -3,12 +3,144 @@ import {
generateColorPalette,
generateSemanticColors,
generateCSSVariables,
+ generateEffectVariables,
} from './colorPalette.js'
-import { borderRadius, boxShadow, fontSize } from './tokens.js'
+import radiusTokens from './generated/radius.json'
+import typographyTokens from './generated/typography.json'
+import effectsData from './generated/effects.json'
let colorPalette = generateColorPalette()
let semanticColors = generateSemanticColors()
-let cssVariables = generateCSSVariables()
+let cssVariables = mergeVariableLayers(
+ generateCSSVariables(),
+ generateEffectVariables(),
+ generateRadiusVariables(),
+)
+
+// Emit `--radius-{key}` for every radius token (numeric scale + aliases) so
+// the values are inspectable as real CSS variables. `borderRadius` is rewired
+// below to consume these vars, so `rounded-4` and `--radius-4` stay in sync.
+function generateRadiusVariables() {
+ const vars = {}
+ for (const [key, value] of Object.entries(radiusTokens)) {
+ if (key === 'DEFAULT') continue
+ vars[`--radius-${key}`] = value
+ }
+ return { ':root': vars }
+}
+
+// Map `DEFAULT` (Tailwind's `rounded` class) onto the numeric var that
+// shares its value, so we don't emit a `--radius-DEFAULT` (awkward name).
+function buildRadiusConfig() {
+ const numericByValue = {}
+ for (const [key, value] of Object.entries(radiusTokens)) {
+ if (/^\d+$/.test(key)) numericByValue[value] = key
+ }
+ const out = {}
+ for (const [key, value] of Object.entries(radiusTokens)) {
+ if (key === 'DEFAULT') {
+ const numeric = numericByValue[value]
+ out[key] = numeric ? `var(--radius-${numeric})` : value
+ } else {
+ out[key] = `var(--radius-${key})`
+ }
+ }
+ return out
+}
+
+// Merge two `{ selector: { var: value } }` objects into one, preserving any
+// vars already declared under the same selector.
+function mergeVariableLayers(...layers) {
+ const out = {}
+ for (const layer of layers) {
+ for (const [selector, vars] of Object.entries(layer)) {
+ out[selector] = { ...(out[selector] || {}), ...vars }
+ }
+ }
+ return out
+}
+
+// Per-size augmentation preserved from pre-Figma config — letterSpacing and
+// fontWeight aren't modelled on `font.size.*` in the Figma export, so we keep
+// the historical values keyed by size name.
+const FONT_SIZE_AUGMENT = {
+ '2xs': { letterSpacing: '0.01em', fontWeight: '420' },
+ xs: { letterSpacing: '0.02em', fontWeight: '420' },
+ sm: { letterSpacing: '0.02em', fontWeight: '420' },
+ base: { letterSpacing: '0.02em', fontWeight: '420' },
+ lg: { letterSpacing: '0.02em', fontWeight: '400' },
+ xl: { letterSpacing: '0.01em', fontWeight: '400' },
+ '2xl': { letterSpacing: '0.01em', fontWeight: '400' },
+ '3xl': { letterSpacing: '0.005em', fontWeight: '400' },
+}
+
+// Tracking for the `medium` weight variant of each size. Figma models typography
+// as named styles (`text/base/medium`, `text/lg/medium`, …) where medium-weight
+// text is tracked tighter than its regular-weight sibling. Tailwind's fontSize
+// utility is keyed by size only, so we expose these as component classes
+// (`.text-base-medium`, `.text-lg-medium`) via `buildTextStyleUtilities()`.
+// Only add entries here for sizes whose medium tracking is confirmed in Figma.
+const FONT_SIZE_MEDIUM_TRACKING = {
+ base: '0.015em',
+ lg: '0.015em',
+}
+
+// Paragraph variants — same sizes but looser line-heights for reading.
+const PARAGRAPH_LINE_HEIGHT = {
+ '2xs': '1.6',
+ xs: '1.6',
+ sm: '1.5',
+ base: '1.5',
+ lg: '1.5',
+ xl: '1.42',
+ '2xl': '1.38',
+ '3xl': '1.2',
+}
+
+function buildFontSize() {
+ const out = {}
+ for (const [key, [size, meta]] of Object.entries(typographyTokens.fontSize)) {
+ // `tiny` arrives with lineHeight 0px from Figma — fall back to a sane ratio.
+ const lineHeight = meta.lineHeight === '0px' ? '1.15' : meta.lineHeight
+ out[key] = [size, { lineHeight, ...(FONT_SIZE_AUGMENT[key] || {}) }]
+ }
+ // Paragraph variants for the size set that defined them previously.
+ for (const [key, lineHeight] of Object.entries(PARAGRAPH_LINE_HEIGHT)) {
+ if (!out[key]) continue
+ const [size, meta] = out[key]
+ out[`p-${key}`] = [size, { ...meta, lineHeight }]
+ }
+ return out
+}
+
+// Focus ring utilities backed by `--focus-*` CSS vars (theme-flipped in
+// colorPalette.js#generateEffectVariables). Registered via `addComponents` so
+// Tailwind IntelliSense picks them up. Usage: `focus-visible:focus-ring-blue`.
+function buildFocusRingUtilities() {
+ const out = {}
+ for (const name of Object.keys(effectsData.focus.light)) {
+ const className = name === 'default' ? '.focus-ring' : `.focus-ring-${name}`
+ out[className] = { boxShadow: `var(--focus-${name})` }
+ }
+ return out
+}
+
+function buildTextStyleUtilities() {
+ const out = {}
+ for (const [key, tracking] of Object.entries(FONT_SIZE_MEDIUM_TRACKING)) {
+ const entry = typographyTokens.fontSize[key]
+ if (!entry) continue
+ const [size, meta] = entry
+ const lineHeight = meta.lineHeight === '0px' ? '1.15' : meta.lineHeight
+ out[`.text-${key}-medium`] = {
+ fontSize: size,
+ lineHeight,
+ fontWeight: '500',
+ letterSpacing: tracking,
+ }
+ }
+ return out
+}
let globalStyles = (theme) => ({
html: {
@@ -49,18 +181,36 @@ export default plugin(
function ({ addBase, addComponents, theme }) {
addBase({ ...globalStyles(theme), ...cssVariables })
addComponents(componentStyles)
+ addComponents(buildTextStyleUtilities())
+ addComponents(buildFocusRingUtilities())
},
{
theme: {
colors: colorPalette,
- borderRadius: borderRadius,
- boxShadow: boxShadow,
+ borderRadius: buildRadiusConfig(),
+ boxShadow: {
+ none: 'none',
+ sm: 'var(--elevation-sm)',
+ base: 'var(--elevation-base)',
+ DEFAULT: 'var(--elevation-base)',
+ md: 'var(--elevation-md)',
+ lg: 'var(--elevation-lg)',
+ xl: 'var(--elevation-xl)',
+ '2xl': 'var(--elevation-2xl)',
+ status: 'var(--elevation-status)',
+ 'dark-sm': 'var(--dark-elevation-sm)',
+ 'dark-base': 'var(--dark-elevation-base)',
+ 'dark-md': 'var(--dark-elevation-md)',
+ 'dark-lg': 'var(--dark-elevation-lg)',
+ 'dark-xl': 'var(--dark-elevation-xl)',
+ 'dark-2xl': 'var(--dark-elevation-2xl)',
+ },
container: {
padding: {
xl: '5rem',
},
},
- fontSize: fontSize,
+ fontSize: buildFontSize(),
screens: {
sm: '640px',
md: '768px',
@@ -253,7 +403,7 @@ export default plugin(
v3: {
css: [
{
- fontSize: '14px',
+ fontSize: '15px',
fontWeight: 420,
lineHeight: '1.7',
letterSpacing: '0.02em',
diff --git a/tailwind/tokens.js b/tailwind/tokens.js
index 97b0c7420..a1390aa80 100644
--- a/tailwind/tokens.js
+++ b/tailwind/tokens.js
@@ -1,14 +1,15 @@
-const borderRadius = {
- none: '0px', // 0
- sm: '0.25rem', // 4px
- DEFAULT: '0.5rem', // 8px
- md: '0.625rem', // 10px
- lg: '0.75rem', // 12px
- xl: '1rem', // 16px
- '2xl': '1.25rem', // 20px
- full: '9999px', // 9999px
-}
+// Public token exports for external consumers (Tailwind v3 presets, CSS-in-JS,
+// design-system tooling). Sourced from the Figma-synced `./generated/*` JSON;
+// the plugin reads the same generated files directly so both stay in sync via
+// `yarn sync-tokens`.
+import radiusTokens from './generated/radius.json'
+import typographyTokens from './generated/typography.json'
+
+const borderRadius = radiusTokens
+// Elevation tokens are flipped per theme by the plugin via CSS vars; the
+// exported shape keeps static values so non-runtime consumers (snapshot tests,
+// docs tooling) still get sensible defaults.
const boxShadow = {
sm: '0px 1px 2px rgba(0, 0, 0, 0.1)',
DEFAULT:
@@ -21,25 +22,46 @@ const boxShadow = {
none: 'none',
}
-const fontSize = {
- '2xs': ['11px', { lineHeight: '1.15', letterSpacing: '0.01em', fontWeight: '420' }],
- xs: ['12px', { lineHeight: '1.15', letterSpacing: '0.02em', fontWeight: '420' }],
- sm: ['13px', { lineHeight: '1.15', letterSpacing: '0.02em', fontWeight: '420' }],
- base: ['14px', { lineHeight: '1.15', letterSpacing: '0.02em', fontWeight: '420' }],
- lg: ['16px', { lineHeight: '1.15', letterSpacing: '0.02em', fontWeight: '400' }],
- xl: ['18px', { lineHeight: '1.15', letterSpacing: '0.01em', fontWeight: '400' }],
- '2xl': ['20px', { lineHeight: '1.15', letterSpacing: '0.01em', fontWeight: '400' }],
- '3xl': ['24px', { lineHeight: '1.15', fontWeight: 400, letterSpacing: '0.005em' }],
- // font size for paragraphs
- 'p-2xs': ['11px', { lineHeight: '1.6', letterSpacing: '0.01em', fontWeight: '420' }],
- 'p-xs': ['12px', { lineHeight: '1.6', letterSpacing: '0.02em', fontWeight: '420' }],
- 'p-sm': ['13px', { lineHeight: '1.5', letterSpacing: '0.02em', fontWeight: '420' }],
- 'p-base': ['14px', { lineHeight: '1.5', letterSpacing: '0.02em', fontWeight: '420' }],
- 'p-lg': ['16px', { lineHeight: '1.5', letterSpacing: '0.02em', fontWeight: '400' }],
- 'p-xl': ['18px', { lineHeight: '1.42', letterSpacing: '0.01em', fontWeight: '400' }],
- 'p-2xl': ['20px', { lineHeight: '1.38', letterSpacing: '0.01em', fontWeight: '400' }],
- 'p-3xl': ['24px', { lineHeight: '1.2', fontWeight: 400, letterSpacing: '0.005em' }],
+// letterSpacing / fontWeight aren't modelled on `font.size.*` in the Figma
+// export — keep the historical values keyed by size name. Kept in sync with
+// the matching map in plugin.js.
+const FONT_SIZE_AUGMENT = {
+ '2xs': { letterSpacing: '0.01em', fontWeight: '420' },
+ xs: { letterSpacing: '0.02em', fontWeight: '420' },
+ sm: { letterSpacing: '0.02em', fontWeight: '420' },
+ base: { letterSpacing: '0.02em', fontWeight: '420' },
+ lg: { letterSpacing: '0.02em', fontWeight: '400' },
+ xl: { letterSpacing: '0.01em', fontWeight: '400' },
+ '2xl': { letterSpacing: '0.01em', fontWeight: '400' },
+ '3xl': { letterSpacing: '0.005em', fontWeight: '400' },
+}
+
+const PARAGRAPH_LINE_HEIGHT = {
+ '2xs': '1.6',
+ xs: '1.6',
+ sm: '1.5',
+ base: '1.5',
+ lg: '1.5',
+ xl: '1.42',
+ '2xl': '1.38',
+ '3xl': '1.2',
}
+function buildFontSize() {
+ const out = {}
+ for (const [key, [size, meta]] of Object.entries(typographyTokens.fontSize)) {
+ const lineHeight = meta.lineHeight === '0px' ? '1.15' : meta.lineHeight
+ out[key] = [size, { lineHeight, ...(FONT_SIZE_AUGMENT[key] || {}) }]
+ }
+ for (const [key, lineHeight] of Object.entries(PARAGRAPH_LINE_HEIGHT)) {
+ if (!out[key]) continue
+ const [size, meta] = out[key]
+ out[`p-${key}`] = [size, { ...meta, lineHeight }]
+ }
+ return out
+}
+
+const fontSize = buildFontSize()
+
export { borderRadius, boxShadow, fontSize }
-export * from "./colorPalette.js"
+export * from './colorPalette.js'