[READ-ONLY] Mirror of https://github.com/lukebennett88/luke-ui. luke-ui.netlify.app/
0

Configure Feed

Select the types of activity you want to include in your feed.

Simplify token reference

Luke Bennett (Jul 18, 2026, 3:48 PM +1000) e95d454f 6a40cc32

+19 -281
-262
apps/docs/src/components/token-explorer.tsx
··· 1 - import { vars } from '@luke-ui/react/theme'; 2 - import type { CSSProperties } from 'react'; 3 - 4 - type TokenGroupId = 5 - | 'content' 6 - | 'depth' 7 - | 'intent' 8 - | 'radius' 9 - | 'spacing' 10 - | 'surfaces' 11 - | 'typography'; 12 - 13 - interface TokenValue { 14 - path: string; 15 - value: string; 16 - } 17 - 18 - interface TokenRow { 19 - label: string; 20 - values: Array<TokenValue>; 21 - } 22 - 23 - interface TokenGroup { 24 - description: string; 25 - id: TokenGroupId; 26 - label: string; 27 - rows: Array<TokenRow>; 28 - } 29 - 30 - const groups: Array<TokenGroup> = [ 31 - { 32 - description: 'Choose the layer an element occupies.', 33 - id: 'surfaces', 34 - label: 'Surfaces', 35 - rows: [ 36 - token('Canvas', 'color.surface.canvas', vars.color.surface.canvas), 37 - token('Recessed', 'color.surface.recessed', vars.color.surface.recessed), 38 - token('Resting', 'color.surface.resting', vars.color.surface.resting), 39 - token('Floating', 'color.surface.floating', vars.color.surface.floating), 40 - token('Overlay', 'color.surface.overlay', vars.color.surface.overlay), 41 - token('Disabled', 'color.surfaceDisabled', vars.color.surfaceDisabled), 42 - token('Loading skeleton', 'color.loadingSkeleton', vars.color.loadingSkeleton), 43 - ], 44 - }, 45 - { 46 - description: 'Choose readable text and borders that describe control state.', 47 - id: 'content', 48 - label: 'Content', 49 - rows: [ 50 - token('Primary text', 'color.text.primary', vars.color.text.primary), 51 - token('Secondary text', 'color.text.secondary', vars.color.text.secondary), 52 - token('Disabled text', 'color.textDisabled', vars.color.textDisabled), 53 - token('Decorative border', 'color.border.decorative', vars.color.border.decorative), 54 - token('Control border', 'color.border.control', vars.color.border.control), 55 - token('Focus border', 'color.border.focus', vars.color.border.focus), 56 - token('Disabled border', 'color.borderDisabled', vars.color.borderDisabled), 57 - ], 58 - }, 59 - { 60 - description: 'Choose the meaning of status and action feedback, not a palette colour.', 61 - id: 'intent', 62 - label: 'Intent', 63 - rows: [ 64 - intent('Neutral', vars.color.intent.neutral, false), 65 - intent('Accent', vars.color.intent.accent, true), 66 - intent('Info', vars.color.intent.info, true), 67 - intent('Success', vars.color.intent.success, true), 68 - intent('Warning', vars.color.intent.warning, true), 69 - intent('Danger', vars.color.intent.danger, true), 70 - ], 71 - }, 72 - { 73 - description: 'Choose coordinated type steps and weight roles.', 74 - id: 'typography', 75 - label: 'Typography', 76 - rows: [ 77 - token('Font family', 'font.family', vars.font.family), 78 - ...([100, 200, 300, 400, 500, 600, 700, 800, 900] as const).map((size) => ({ 79 - label: `${size} type step`, 80 - values: [ 81 - { path: `font.${size}.fontSize`, value: vars.font[size].fontSize }, 82 - { path: `font.${size}.lineHeight`, value: vars.font[size].lineHeight }, 83 - { path: `font.${size}.letterSpacing`, value: vars.font[size].letterSpacing }, 84 - ], 85 - })), 86 - token('Body weight', 'font.weight.body', vars.font.weight.body), 87 - token('Label weight', 'font.weight.label', vars.font.weight.label), 88 - token('Heading weight', 'font.weight.heading', vars.font.weight.heading), 89 - token('Emphasis weight', 'font.weight.emphasis', vars.font.weight.emphasis), 90 - ], 91 - }, 92 - { 93 - description: 'Choose the spacing step that matches the separation between elements.', 94 - id: 'spacing', 95 - label: 'Spacing', 96 - rows: ([100, 200, 300, 400, 600, 800, 1000, 1200, 1600] as const).map((size) => 97 - token(`${size}`, `space.${size}`, vars.space[size]), 98 - ), 99 - }, 100 - { 101 - description: 102 - 'Choose a radius for a detail, control, surface, overlay, or fully rounded element.', 103 - id: 'radius', 104 - label: 'Radius', 105 - rows: [ 106 - token('Detail', 'radius.detail', vars.radius.detail), 107 - token('Control', 'radius.control', vars.radius.control), 108 - token('Surface', 'radius.surface', vars.radius.surface), 109 - token('Overlay', 'radius.overlay', vars.radius.overlay), 110 - token('Full', 'radius.full', vars.radius.full), 111 - ], 112 - }, 113 - { 114 - description: 'Choose the elevation of a surface instead of assembling a shadow.', 115 - id: 'depth', 116 - label: 'Depth', 117 - rows: [ 118 - token('Recessed', 'depth.recessed', vars.depth.recessed), 119 - token('Resting', 'depth.resting', vars.depth.resting), 120 - token('Raised', 'depth.raised', vars.depth.raised), 121 - token('Floating', 'depth.floating', vars.depth.floating), 122 - token('Overlay', 'depth.overlay', vars.depth.overlay), 123 - ], 124 - }, 125 - ]; 126 - 127 - export function TokenExplorer() { 128 - return ( 129 - <section className="not-prose grid gap-3 rounded-lg border border-fd-border bg-fd-card p-4"> 130 - {groups.map((group) => ( 131 - <TokenGroup group={group} key={group.id} /> 132 - ))} 133 - </section> 134 - ); 135 - } 136 - 137 - function TokenGroup({ group }: { group: TokenGroup }) { 138 - return ( 139 - <details className="rounded-md border border-fd-border" open={group.id === 'surfaces'}> 140 - <summary className="cursor-pointer p-3"> 141 - <h3 className="m-0 font-semibold text-fd-foreground">{group.label}</h3> 142 - <p className="mb-0 mt-1 text-fd-muted-foreground text-sm">{group.description}</p> 143 - </summary> 144 - <div className="overflow-x-auto border-fd-border border-t"> 145 - <table className="w-full min-w-[40rem] border-collapse text-sm"> 146 - <thead className="bg-fd-muted"> 147 - <tr> 148 - <th className="p-3 text-left font-medium">Effect</th> 149 - <th className="p-3 text-left font-medium">Purpose</th> 150 - <th className="p-3 text-left font-medium">Public variable</th> 151 - </tr> 152 - </thead> 153 - <tbody> 154 - {group.rows.map((row) => ( 155 - <tr className="border-fd-border border-t" key={row.label}> 156 - <td className="p-3"> 157 - <TokenPreview groupId={group.id} row={row} /> 158 - </td> 159 - <td className="p-3 text-fd-foreground">{row.label}</td> 160 - <td className="p-3 font-mono text-xs text-fd-muted-foreground"> 161 - {row.values.map((value) => ( 162 - <div key={value.path}>{customPropertyName(value.value)}</div> 163 - ))} 164 - </td> 165 - </tr> 166 - ))} 167 - </tbody> 168 - </table> 169 - </div> 170 - </details> 171 - ); 172 - } 173 - 174 - function TokenPreview({ groupId, row }: { groupId: TokenGroupId; row: TokenRow }) { 175 - const value = row.values[0]?.value; 176 - if (!value) return null; 177 - 178 - const style = previewStyle(groupId, row, value); 179 - return ( 180 - <div 181 - aria-label={`${row.label} token preview`} 182 - className="flex h-10 w-24 items-center justify-center border border-fd-border text-xs" 183 - role="img" 184 - style={style} 185 - > 186 - {groupId === 'typography' ? 'Aa' : null} 187 - </div> 188 - ); 189 - } 190 - 191 - function previewStyle(groupId: TokenGroupId, row: TokenRow, value: string): CSSProperties { 192 - if (groupId === 'content') { 193 - return row.label.includes('border') 194 - ? { backgroundColor: vars.color.surface.resting, borderColor: value, borderWidth: 3 } 195 - : { backgroundColor: value }; 196 - } 197 - if (groupId === 'typography') { 198 - if (row.label.endsWith('type step')) { 199 - return { 200 - fontFamily: vars.font.family, 201 - fontSize: value, 202 - letterSpacing: row.values[2]?.value, 203 - lineHeight: row.values[1]?.value, 204 - }; 205 - } 206 - return row.label.endsWith('weight') ? { fontWeight: value } : { fontFamily: value }; 207 - } 208 - if (groupId === 'spacing') { 209 - return { backgroundColor: vars.color.intent.accent.surface.solid, inlineSize: value }; 210 - } 211 - if (groupId === 'radius') 212 - return { backgroundColor: vars.color.surface.recessed, borderRadius: value }; 213 - if (groupId === 'depth') return { backgroundColor: vars.color.surface.resting, boxShadow: value }; 214 - if (groupId === 'intent') return { backgroundColor: value }; 215 - return { backgroundColor: value }; 216 - } 217 - 218 - function intent( 219 - label: string, 220 - value: { 221 - onSolid: string; 222 - surface: { 223 - solid: string; 224 - solidHover: string; 225 - solidPressed: string; 226 - subtle: string; 227 - subtleHover: string; 228 - subtlePressed: string; 229 - }; 230 - }, 231 - hasContentRoles: boolean, 232 - ): TokenRow { 233 - const path = label.toLowerCase(); 234 - const values = [ 235 - { path: `color.intent.${path}.surface.subtle`, value: value.surface.subtle }, 236 - { path: `color.intent.${path}.surface.subtleHover`, value: value.surface.subtleHover }, 237 - { path: `color.intent.${path}.surface.subtlePressed`, value: value.surface.subtlePressed }, 238 - { path: `color.intent.${path}.surface.solid`, value: value.surface.solid }, 239 - { path: `color.intent.${path}.surface.solidHover`, value: value.surface.solidHover }, 240 - { path: `color.intent.${path}.surface.solidPressed`, value: value.surface.solidPressed }, 241 - { path: `color.intent.${path}.onSolid`, value: value.onSolid }, 242 - ]; 243 - if (!hasContentRoles) return { label, values }; 244 - 245 - const roles = value as typeof value & { border: string; text: string; textHover?: string }; 246 - values.push( 247 - { path: `color.intent.${path}.border`, value: roles.border }, 248 - { path: `color.intent.${path}.text`, value: roles.text }, 249 - ); 250 - if (roles.textHover) 251 - values.push({ path: `color.intent.${path}.textHover`, value: roles.textHover }); 252 - return { label, values }; 253 - } 254 - 255 - function token(label: string, path: string, value: string): TokenRow { 256 - return { label, values: [{ path, value }] }; 257 - } 258 - 259 - function customPropertyName(value: string): string { 260 - const match = /^var\((--[^,)]+)/.exec(value); 261 - return match?.[1] ?? value; 262 - }
-2
apps/docs/src/routes/$.tsx
··· 13 13 import { ExampleBlock } from '../components/example-block'; 14 14 import { PageActions } from '../components/page-actions'; 15 15 import { SourceCodeBlock } from '../components/source-code-block'; 16 - import { TokenExplorer } from '../components/token-explorer'; 17 16 import { baseOptions } from '../lib/layout.shared'; 18 17 import { source } from '../lib/source'; 19 18 import { getStorybookStoryUrl, withBasePath } from '../lib/storybook'; ··· 25 24 AutoTypeTable, 26 25 ExampleBlock, 27 26 SourceCodeBlock, 28 - TokenExplorer, 29 27 TypeTable, 30 28 }; 31 29
+19 -17
apps/docs/content/docs/theming/token-reference.mdx
··· 23 23 A warning message might use the warning intent's subtle surface and text. Avoid carrying resolved 24 24 colour values into application CSS. 25 25 26 - ## Explore semantic variables 27 - 28 - The explorer uses the active docs identity and colour mode. Pick a purpose to see the variables a 29 - custom element can use and their visible effect. 30 - 31 - <TokenExplorer /> 32 - 33 - The explorer shows the most common purposes. The complete contract, including motion, icon sizes, 34 - and control sizes, is in the table below. Generated palette values, component recipe selectors, and 35 - theme-foundation inputs are implementation details, not application styling APIs. 36 - 37 26 ## Tokens 38 27 39 28 This table is generated from the public TypeScript declaration and JSDoc, so it stays aligned with 40 - the code that produces each theme. 29 + the theme code. It includes motion, icon sizes, and control sizes. Generated palette values, 30 + component recipe selectors, and theme-foundation inputs are not public styling APIs. 41 31 42 32 <auto-type-table 43 33 path="apps/docs/src/generated/token-reference.generated.ts" ··· 46 36 47 37 ## Next steps 48 38 49 - Read [Colour](/overview/color) for surfaces, content, and intent. Read 50 - [Typography](/overview/typography) for the type scale and [Layout](/overview/layout) for spacing. 51 - [Theme](/overview/theme) explains radii and depth as part of an identity. Read 52 - [Authoring a theme](/theming/authoring) to generate the complete contract for a product-owned 53 - identity. 39 + <Cards> 40 + <Card href="/overview/color" title="Colour"> 41 + Choose surface, content, border, and intent roles. 42 + </Card> 43 + <Card href="/overview/typography" title="Typography"> 44 + Use the theme's type scale and weight roles. 45 + </Card> 46 + <Card href="/overview/layout" title="Layout"> 47 + Apply spacing through Box and layout utilities. 48 + </Card> 49 + <Card href="/overview/theme" title="Theming overview"> 50 + Understand identity, colour mode, radius, and depth. 51 + </Card> 52 + <Card href="/theming/authoring" title="Author a theme"> 53 + Generate a product-owned theme stylesheet. 54 + </Card> 55 + </Cards>