@latty-ds/tokens 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -116,17 +116,17 @@ console.log(tokens.elevation['2']); // "0 4px 6px -1px rgb(…)"
116
116
 
117
117
  ### Primitive tokens (`tokens.css`)
118
118
 
119
- | Prefix | Example | Description |
120
- | --------------------------------------- | ------------------------------ | --------------------------------- |
121
- | `--lt-color-[name]-[step]` | `--lt-color-primary-500` | Color palette steps 50–900 |
122
- | `--lt-color-[name]-muted-[step]` | `--lt-color-primary-muted-300` | Desaturated variant of each color |
123
- | `--lt-color-neutral-[step]` | `--lt-color-neutral-100` | Greyscale scale |
124
- | `--lt-color-white` / `--lt-color-black` | | System colors |
125
- | `--lt-spacing-[n]` | `--lt-spacing-4` → `1rem` | Spacing scale (rem), n = 0–24 |
126
- | `--lt-spacing-px-[n]` | `--lt-spacing-px-4` → `16px` | Spacing scale (px) |
127
- | `--lt-border-radius` | `0.5rem` | Global border radius |
128
- | `--lt-elevation-[n]` | `--lt-elevation-2` | Box shadows, n = 0–5 |
129
- | `--lt-typography-fontFamily` | `"Hanken Grotesk", sans-serif` | Base font family |
119
+ | Prefix | Example | Description |
120
+ | --------------------------------------- | ------------------------------ | -------------------------------------------------------------------------- |
121
+ | `--lt-color-[name]-[step]` | `--lt-color-primary-500` | Color palette steps 50–900 |
122
+ | `--lt-color-[name]-muted-[step]` | `--lt-color-primary-muted-300` | Desaturated variant of each color |
123
+ | `--lt-color-neutral-[step]` | `--lt-color-neutral-100` | Greyscale scale |
124
+ | `--lt-color-white` / `--lt-color-black` | | System colors |
125
+ | `--lt-spacing-[n]` | `--lt-spacing-4` → `1rem` | Spacing scale (rem), n = 0–24 |
126
+ | `--lt-spacing-px-[n]` | `--lt-spacing-px-4` → `16px` | Spacing scale (px) |
127
+ | `--lt-border-radius` | `0.5rem` | Global border radius |
128
+ | `--lt-elevation-[n]` | `--lt-elevation-2` | Box shadows, n = 0–5 |
129
+ | `--lt-typography-fontFamilyPrimary` | `"Hanken Grotesk", sans-serif` | Base font family (additional `...Secondary`/`...Tertiary` when configured) |
130
130
 
131
131
  Color names: `primary`, `secondary`, `success`, `warning`, `error`, `info` (each with a `-muted` variant).
132
132
 
@@ -4,22 +4,48 @@
4
4
  * Any other CSS length (e.g. `'1.5px'`) passes through unchanged.
5
5
  */
6
6
  type BorderWidth = 'thin' | 'medium' | 'thick' | (string & {});
7
+ /** Generic CSS font-family keyword used as the safety-net fallback if a CDN font fails to load. */
8
+ type FontFallback = 'sans-serif' | 'serif' | 'monospace' | 'cursive' | 'fantasy' | 'system-ui';
9
+ /**
10
+ * A single `font.family` entry: a plain CSS value, a Google Fonts CSS2 URL, or
11
+ * a URL paired with a custom generic fallback (default `'sans-serif'`) for
12
+ * when the derived family isn't sans-serif — e.g. a serif display font.
13
+ */
14
+ type FontFamilyEntry = string | {
15
+ url: string;
16
+ fallback?: FontFallback;
17
+ };
7
18
  interface LattyConfig {
8
19
  /** Built-in names: primary, secondary, success, warning, error, info. Any additional name generates a new palette. */
9
20
  colors?: Record<string, string>;
10
21
  font?: {
11
22
  /**
12
- * A CSS `font-family` value (e.g. `'Inter, sans-serif'`), or a Google
13
- * Fonts CSS2 stylesheet URL (e.g.
14
- * `'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900&display=swap'`).
15
- * URLs get `@import`ed automatically and the family name is derived from
16
- * the `family=` query param — no separate `<link>` tag needed. Stylesheet
17
- * URLs from providers other than `fonts.googleapis.com` are still loaded,
18
- * but since there's no reliable way to guess the family name from an
19
- * arbitrary CDN, pass the resulting name as a plain value instead (or
20
- * open an issue if there's a provider worth special-casing).
23
+ * A CSS `font-family` value (e.g. `'Inter, sans-serif'`), a Google Fonts
24
+ * CSS2 stylesheet URL (e.g.
25
+ * `'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900&display=swap'`),
26
+ * a `{ url, fallback }` object, or an array of any of these. URLs get
27
+ * `@import`ed automatically and the family name is derived from the
28
+ * `family=` query param — no separate `<link>` tag needed. Stylesheet
29
+ * URLs from providers other than `fonts.googleapis.com` are still
30
+ * loaded, but since there's no reliable way to guess the family name
31
+ * from an arbitrary CDN, pass the resulting name as a plain value
32
+ * instead (or open an issue if there's a provider worth special-casing).
33
+ *
34
+ * A bare URL string always falls back to the generic `sans-serif` family
35
+ * if the font itself fails to load. Use the `{ url, fallback }` object
36
+ * form to pick a different generic fallback (e.g. `'serif'` for a serif
37
+ * display font):
38
+ * ```ts
39
+ * family: { url: 'https://fonts.googleapis.com/css2?family=Playfair+Display', fallback: 'serif' }
40
+ * ```
41
+ *
42
+ * When an array is passed, each entry maps to its own token in order —
43
+ * `--lt-typography-fontFamilyPrimary`, `...Secondary`, `...Tertiary`,
44
+ * etc. — rather than being combined into a single CSS fallback stack, so
45
+ * multiple fonts (e.g. a heading font and a body font) can each be
46
+ * referenced independently in your own CSS.
21
47
  */
22
- family?: string;
48
+ family?: FontFamilyEntry | FontFamilyEntry[];
23
49
  };
24
50
  border?: {
25
51
  radius?: string;
@@ -45,30 +71,26 @@ declare const createStyleSheet: (userConfig?: LattyConfig) => string;
45
71
  * Injects design tokens into the document as a `<style>` element.
46
72
  * Call once at your app's entry point — before any components render.
47
73
  *
48
- * Add `data-lt` to `<html>` to enable automatic FOUC prevention: configure()
49
- * hides the document during token injection and reveals it on the next frame.
50
- * For zero FOUC on initial load, also add this one-liner before your module script:
51
- * `<style>html[data-lt]:not([data-lt-ready]){visibility:hidden}</style>`
52
- *
53
- * `font.family` also accepts a Google Fonts CSS2 stylesheet URL — configure()
54
- * both `@import`s it and derives the font-family name for you, so no separate
55
- * `<link>` tag is needed.
74
+ * `font.family` accepts a single value or an array. Each entry is either a
75
+ * plain CSS `font-family` value or a Google Fonts CSS2 stylesheet URL (which
76
+ * configure() `@import`s and derives the family name from automatically — no
77
+ * separate `<link>` tag needed). Array entries map to their own token in
78
+ * order — `--lt-typography-fontFamilyPrimary`, `...Secondary`, `...Tertiary`,
79
+ * etc. — so each font can be referenced independently in your own CSS.
56
80
  *
57
81
  * @example
58
82
  * ```html
59
- * <html lang="en" data-lt>
60
- * <head>
61
- * <style>html[data-lt]:not([data-lt-ready]){visibility:hidden}</style>
62
- * <script type="module">
63
- * import { configure } from '@latty-ds/tokens/configure';
64
- * configure({
65
- * colors: { primary: '#6366f1', secondary: '#f59e0b' },
66
- * font: { family: 'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap' },
67
- * border: { radius: '0.375rem', width: 'medium' },
68
- * });
69
- * </script>
70
- * </head>
71
- * </html>
83
+ * <script type="module">
84
+ * import { configure } from '@latty-ds/tokens/configure';
85
+ * configure({
86
+ * colors: { primary: '#6366f1', secondary: '#f59e0b' },
87
+ * font: { family: [
88
+ * 'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap',
89
+ * 'Georgia, serif',
90
+ * ] },
91
+ * border: { radius: '0.375rem', width: 'medium' },
92
+ * });
93
+ * </script>
72
94
  * ```
73
95
  */
74
96
  declare const configure: (userConfig?: LattyConfig) => void;
package/dist/configure.js CHANGED
@@ -172,7 +172,7 @@ var buildTokens = (cfg) => {
172
172
  color = addSystemColors(color);
173
173
  const spacing = buildSpacing();
174
174
  const border = { radius: DEFAULT_BORDER_RADIUS, width: DEFAULT_BORDER_WIDTH };
175
- const typography = { fontFamily: DEFAULT_FONT_FAMILY };
175
+ const typography = { fontFamilyPrimary: DEFAULT_FONT_FAMILY };
176
176
  const elevation = buildElevation(color.neutral);
177
177
  return { color, spacing, border, typography, elevation };
178
178
  };
@@ -320,6 +320,8 @@ var buildDarkSemanticTokens = (opts = {}) => {
320
320
  var buildSemanticTokens = (mode = "light", opts = {}) => mode === "dark" ? buildDarkSemanticTokens(opts) : buildLightSemanticTokens(opts);
321
321
 
322
322
  // src/configure/font.ts
323
+ var FONT_FAMILY_SLOT_NAMES = ["Primary", "Secondary", "Tertiary", "Quaternary"];
324
+ var fontFamilySlot = (index) => `fontFamily${FONT_FAMILY_SLOT_NAMES[index] ?? String(index + 1)}`;
323
325
  var unwrapQuotes = (value) => {
324
326
  const trimmed = value.trim();
325
327
  const first = trimmed[0];
@@ -331,8 +333,10 @@ var unwrapQuotes = (value) => {
331
333
  var isUrl = (value) => /^https?:\/\//i.test(value);
332
334
  var namesFromFamilyParam = (raw) => raw.split("|").map((part) => part.split(":")[0].trim()).filter(Boolean);
333
335
  var quoteIfNeeded = (name) => /\s/.test(name) ? `"${name}"` : name;
334
- var resolveFontFamily = (value) => {
335
- const unwrapped = unwrapQuotes(value);
336
+ var resolveSingleFont = (entry) => {
337
+ const rawValue = typeof entry === "string" ? entry : entry.url;
338
+ const fallback = typeof entry === "string" ? "sans-serif" : entry.fallback ?? "sans-serif";
339
+ const unwrapped = unwrapQuotes(rawValue);
336
340
  if (!isUrl(unwrapped)) return { family: unwrapped, importUrl: null };
337
341
  let parsed;
338
342
  try {
@@ -343,11 +347,12 @@ var resolveFontFamily = (value) => {
343
347
  if (parsed.hostname.toLowerCase() === "fonts.googleapis.com") {
344
348
  const names = [...new Set(parsed.searchParams.getAll("family").flatMap(namesFromFamilyParam))];
345
349
  if (names.length > 0) {
346
- return { family: `${names.map(quoteIfNeeded).join(", ")}, sans-serif`, importUrl: unwrapped };
350
+ return { family: `${names.map(quoteIfNeeded).join(", ")}, ${fallback}`, importUrl: unwrapped };
347
351
  }
348
352
  }
349
353
  return { family: "", importUrl: unwrapped };
350
354
  };
355
+ var resolveFontFamilies = (value) => (Array.isArray(value) ? value : [value]).map(resolveSingleFont);
351
356
 
352
357
  // tokens.config.json
353
358
  var tokens_config_default = {
@@ -377,12 +382,16 @@ var resolveBorderWidth = (width) => BORDER_WIDTH_PRESETS[width] ?? width;
377
382
  var createStyleSheet = (userConfig = {}) => {
378
383
  const cfg = toInternalConfig(userConfig);
379
384
  let tokens = buildTokens(cfg);
380
- let fontImportUrl = null;
385
+ const importUrls = [];
381
386
  if (userConfig.font?.family) {
382
- const resolvedFont = resolveFontFamily(userConfig.font.family);
383
- fontImportUrl = resolvedFont.importUrl;
384
- if (resolvedFont.family) {
385
- tokens = { ...tokens, typography: { fontFamily: resolvedFont.family } };
387
+ const resolvedFonts = resolveFontFamilies(userConfig.font.family);
388
+ const typographyOverrides = {};
389
+ resolvedFonts.forEach((font, i) => {
390
+ if (font.importUrl) importUrls.push(font.importUrl);
391
+ if (font.family) typographyOverrides[fontFamilySlot(i)] = font.family;
392
+ });
393
+ if (Object.keys(typographyOverrides).length > 0) {
394
+ tokens = { ...tokens, typography: { ...tokens.typography, ...typographyOverrides } };
386
395
  }
387
396
  }
388
397
  if (userConfig.border?.radius) {
@@ -403,22 +412,18 @@ var createStyleSheet = (userConfig = {}) => {
403
412
  } else {
404
413
  body = primitives + "\n" + semanticTokensToCss(buildSemanticTokens("light", semanticOpts)) + "\n" + semanticTokensToCss(buildSemanticTokens("dark", semanticOpts), "@media (prefers-color-scheme: dark)") + "\n" + semanticTokensToCss(buildSemanticTokens("dark", semanticOpts), '[data-theme="dark"]') + "\n" + semanticTokensToCss(buildSemanticTokens("light", semanticOpts), '[data-theme="light"]');
405
414
  }
406
- return fontImportUrl ? `@import url("${fontImportUrl}");
415
+ const importLines = importUrls.map((url) => `@import url("${url}");`).join("\n");
416
+ return importLines ? `${importLines}
407
417
  ${body}` : body;
408
418
  };
409
419
  var configure = (userConfig = {}) => {
410
- const root = document.documentElement;
411
- root.removeAttribute("data-lt-ready");
412
420
  let style = document.getElementById("lt-tokens");
413
421
  if (!style) {
414
422
  style = document.createElement("style");
415
423
  style.id = "lt-tokens";
416
424
  document.head.prepend(style);
417
425
  }
418
- const guard = "html[data-lt]:not([data-lt-ready]){visibility:hidden}\n";
419
- const css = createStyleSheet(userConfig);
420
- style.textContent = css.startsWith("@import") ? css.replace(/^(@import[^\n]*\n)/, `$1${guard}`) : guard + css;
421
- requestAnimationFrame(() => root.setAttribute("data-lt-ready", ""));
426
+ style.textContent = createStyleSheet(userConfig);
422
427
  };
423
428
  export {
424
429
  configure,
package/dist/tokens.css CHANGED
@@ -189,5 +189,5 @@
189
189
  --lt-spacing-7: 1.75rem;
190
190
  --lt-spacing-8: 2rem;
191
191
  --lt-spacing-9: 2.25rem;
192
- --lt-typography-fontFamily: "Hanken Grotesk", sans-serif;
192
+ --lt-typography-fontFamilyPrimary: "Hanken Grotesk", sans-serif;
193
193
  }
package/dist/tokens.js CHANGED
@@ -1 +1 @@
1
- export const tokens = {"color":{"primary":{"50":"#f0e5df","100":"#ffd4b5","200":"#ffbb87","300":"#ffa860","400":"#ff9539","500":"#ff8200","600":"#d56400","700":"#963b00","800":"#5e3c25","900":"#3c1d05"},"primary-muted":{"50":"#e9e7e6","100":"#e8dfda","200":"#dfd1c8","300":"#dac8bd","400":"#d2beb2","500":"#c6b1a5","600":"#a08e82","700":"#6b5c53","800":"#50453e","900":"#2b2521"},"secondary":{"50":"#e5e7f1","100":"#cbd2ff","200":"#a3acf7","300":"#8a93f4","400":"#7076e5","500":"#5252c5","600":"#4544ad","700":"#333187","800":"#2e3254","900":"#202343"},"secondary-muted":{"50":"#e7e8e9","100":"#d5d7df","200":"#b4b7c4","300":"#a0a3b3","400":"#898c9e","500":"#6c6f81","600":"#5a5d6d","700":"#414351","800":"#363742","900":"#25262c"},"success":{"50":"#e1ebe2","100":"#beedc6","200":"#94e4a4","300":"#70df8b","400":"#4bd573","500":"#22c55e","600":"#00a244","700":"#006f22","800":"#2b4d32","900":"#0c2e16"},"success-muted":{"50":"#e7e8e7","100":"#dbe3dc","200":"#cad7cc","300":"#bfcfc1","400":"#b3c5b6","500":"#a5b8a8","600":"#839586","700":"#546256","800":"#404a41","900":"#222823"},"warning":{"50":"#ece7de","100":"#f5e0b5","200":"#f6d289","300":"#f9c962","400":"#f7bf3a","500":"#eeb308","600":"#c38e00","700":"#845a00","800":"#584720","900":"#332400"},"warning-muted":{"50":"#e8e8e6","100":"#e7e4dc","200":"#e2dcd0","300":"#ded7c8","400":"#dad1c0","500":"#d2c9b8","600":"#a9a090","700":"#6d6659","800":"#504c42","900":"#292621"},"error":{"50":"#f3e4e2","100":"#ffc8c1","200":"#ffa198","300":"#ff847b","400":"#ff6560","500":"#ef4444","600":"#cb2d31","700":"#950e18","800":"#5a312e","900":"#3f1917"},"error-muted":{"50":"#e9e7e7","100":"#e6dad8","200":"#d6c3c1","300":"#cdb4b2","400":"#bfa4a1","500":"#ab908d","600":"#8d7572","700":"#624e4c","800":"#4c3d3b","900":"#2d2423"},"info":{"50":"#e1e9ef","100":"#bce4fe","200":"#8fd2fd","300":"#6ac7ff","400":"#43b9fa","500":"#0ea5e9","600":"#0087c4","700":"#005c8d","800":"#1e465d","900":"#082a3c"},"info-muted":{"50":"#e7e8e9","100":"#d9e0e5","200":"#c5d0d7","300":"#b8c6cf","400":"#aab9c4","500":"#99a9b3","600":"#7a8993","700":"#4f5b63","800":"#3d464c","900":"#22272a"},"neutral":{"50":"#fafafa","100":"#f3f3f3","200":"#e4e4e4","300":"#cacaca","400":"#ababab","500":"#868686","600":"#696969","700":"#4d4d4d","800":"#333333","900":"#1b1b1b"},"white":"#FFFFFF","black":"#000000"},"spacing":{"px":{"0":"0px","1":"4px","2":"8px","3":"12px","4":"16px","5":"20px","6":"24px","7":"28px","8":"32px","9":"36px","10":"40px","11":"44px","12":"48px","13":"52px","14":"56px","15":"60px","16":"64px","17":"68px","18":"72px","19":"76px","20":"80px","21":"84px","22":"88px","23":"92px","24":"96px"},"rem":{"0":"0rem","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","13":"3.25rem","14":"3.5rem","15":"3.75rem","16":"4rem","17":"4.25rem","18":"4.5rem","19":"4.75rem","20":"5rem","21":"5.25rem","22":"5.5rem","23":"5.75rem","24":"6rem"}},"border":{"radius":"0.5rem","width":"1px"},"typography":{"fontFamily":"\"Hanken Grotesk\", sans-serif"},"elevation":{"0":"none","1":"0 1px 3px 0 rgb(27 27 27 / 0.1), 0 1px 2px 0 rgb(27 27 27 / 0.06)","2":"0 4px 6px -1px rgb(27 27 27 / 0.1), 0 2px 4px -1px rgb(27 27 27 / 0.06)","3":"0 10px 15px -3px rgb(27 27 27 / 0.1), 0 4px 6px -2px rgb(27 27 27 / 0.05)","4":"0 20px 25px -5px rgb(27 27 27 / 0.1), 0 10px 10px -5px rgb(27 27 27 / 0.04)","5":"0 25px 50px -12px rgb(27 27 27 / 0.25)"},"semantic":{"text-default":"color-neutral-900","text-subtle":"color-neutral-600","text-muted":"color-neutral-400","text-disabled":"color-neutral-400","text-inverse":"color-white","text-neutral":"color-neutral-700","text-primary":"color-primary-700","text-on-primary":"color-neutral-900","text-secondary":"color-secondary-700","text-on-secondary":"color-white","text-success":"color-success-700","text-on-success":"color-white","text-warning":"color-warning-700","text-on-warning":"color-warning-800","text-error":"color-error-700","text-on-error":"color-white","text-info":"color-info-700","text-on-info":"color-white","bg-default":"color-white","bg-subtle":"color-neutral-50","bg-surface":"color-neutral-100","bg-overlay":"color-neutral-200","bg-inverse":"color-neutral-900","bg-neutral-subtle":"color-neutral-100","bg-primary":"color-primary-500","bg-primary-subtle":"color-primary-100","bg-secondary":"color-secondary-500","bg-secondary-subtle":"color-secondary-100","bg-success":"color-success-500","bg-success-subtle":"color-success-100","bg-warning":"color-warning-500","bg-warning-subtle":"color-warning-100","bg-error":"color-error-500","bg-error-subtle":"color-error-100","bg-info":"color-info-500","bg-info-subtle":"color-info-100","border-default":"color-neutral-200","border-strong":"color-neutral-400","border-subtle":"color-neutral-100","border-focus":"color-primary-200","border-primary":"color-primary-200","border-primary-strong":"color-primary-500","border-secondary":"color-secondary-200","border-secondary-strong":"color-secondary-500","border-success":"color-success-200","border-success-strong":"color-success-500","border-warning":"color-warning-200","border-warning-strong":"color-warning-500","border-error":"color-error-200","border-error-strong":"color-error-500","border-info":"color-info-200","border-info-strong":"color-info-500","interactive-primary-bg":"color-primary-500","interactive-primary-bg-hover":"color-primary-600","interactive-primary-bg-active":"color-primary-700","interactive-secondary-bg":"color-secondary-500","interactive-secondary-bg-hover":"color-secondary-600","interactive-secondary-bg-active":"color-secondary-700","interactive-success-bg":"color-success-500","interactive-success-bg-hover":"color-success-600","interactive-success-bg-active":"color-success-700","interactive-warning-bg":"color-warning-500","interactive-warning-bg-hover":"color-warning-600","interactive-warning-bg-active":"color-warning-700","interactive-error-bg":"color-error-500","interactive-error-bg-hover":"color-error-600","interactive-error-bg-active":"color-error-700","interactive-info-bg":"color-info-500","interactive-info-bg-hover":"color-info-600","interactive-info-bg-active":"color-info-700"},"semanticDark":{"text-default":"color-neutral-100","text-subtle":"color-neutral-300","text-muted":"color-neutral-500","text-disabled":"color-neutral-600","text-inverse":"color-neutral-900","text-neutral":"color-neutral-200","text-primary":"color-primary-300","text-on-primary":"color-neutral-900","text-secondary":"color-secondary-300","text-on-secondary":"color-white","text-success":"color-success-300","text-on-success":"color-white","text-warning":"color-warning-300","text-on-warning":"color-warning-800","text-error":"color-error-300","text-on-error":"color-white","text-info":"color-info-300","text-on-info":"color-white","bg-default":"color-neutral-900","bg-subtle":"color-neutral-800","bg-surface":"color-neutral-800","bg-overlay":"color-neutral-700","bg-inverse":"color-neutral-50","bg-neutral-subtle":"color-neutral-800","bg-primary":"color-primary-500","bg-primary-subtle":"color-primary-900","bg-secondary":"color-secondary-500","bg-secondary-subtle":"color-secondary-900","bg-success":"color-success-500","bg-success-subtle":"color-success-900","bg-warning":"color-warning-500","bg-warning-subtle":"color-warning-900","bg-error":"color-error-500","bg-error-subtle":"color-error-900","bg-info":"color-info-500","bg-info-subtle":"color-info-900","border-default":"color-neutral-700","border-strong":"color-neutral-500","border-subtle":"color-neutral-800","border-focus":"color-primary-400","border-primary":"color-primary-700","border-primary-strong":"color-primary-500","border-secondary":"color-secondary-700","border-secondary-strong":"color-secondary-500","border-success":"color-success-700","border-success-strong":"color-success-500","border-warning":"color-warning-700","border-warning-strong":"color-warning-500","border-error":"color-error-700","border-error-strong":"color-error-500","border-info":"color-info-700","border-info-strong":"color-info-500","interactive-primary-bg":"color-primary-400","interactive-primary-bg-hover":"color-primary-300","interactive-primary-bg-active":"color-primary-500","interactive-secondary-bg":"color-secondary-400","interactive-secondary-bg-hover":"color-secondary-300","interactive-secondary-bg-active":"color-secondary-500","interactive-success-bg":"color-success-400","interactive-success-bg-hover":"color-success-300","interactive-success-bg-active":"color-success-500","interactive-warning-bg":"color-warning-400","interactive-warning-bg-hover":"color-warning-300","interactive-warning-bg-active":"color-warning-500","interactive-error-bg":"color-error-400","interactive-error-bg-hover":"color-error-300","interactive-error-bg-active":"color-error-500","interactive-info-bg":"color-info-400","interactive-info-bg-hover":"color-info-300","interactive-info-bg-active":"color-info-500"}};
1
+ export const tokens = {"color":{"primary":{"50":"#f0e5df","100":"#ffd4b5","200":"#ffbb87","300":"#ffa860","400":"#ff9539","500":"#ff8200","600":"#d56400","700":"#963b00","800":"#5e3c25","900":"#3c1d05"},"primary-muted":{"50":"#e9e7e6","100":"#e8dfda","200":"#dfd1c8","300":"#dac8bd","400":"#d2beb2","500":"#c6b1a5","600":"#a08e82","700":"#6b5c53","800":"#50453e","900":"#2b2521"},"secondary":{"50":"#e5e7f1","100":"#cbd2ff","200":"#a3acf7","300":"#8a93f4","400":"#7076e5","500":"#5252c5","600":"#4544ad","700":"#333187","800":"#2e3254","900":"#202343"},"secondary-muted":{"50":"#e7e8e9","100":"#d5d7df","200":"#b4b7c4","300":"#a0a3b3","400":"#898c9e","500":"#6c6f81","600":"#5a5d6d","700":"#414351","800":"#363742","900":"#25262c"},"success":{"50":"#e1ebe2","100":"#beedc6","200":"#94e4a4","300":"#70df8b","400":"#4bd573","500":"#22c55e","600":"#00a244","700":"#006f22","800":"#2b4d32","900":"#0c2e16"},"success-muted":{"50":"#e7e8e7","100":"#dbe3dc","200":"#cad7cc","300":"#bfcfc1","400":"#b3c5b6","500":"#a5b8a8","600":"#839586","700":"#546256","800":"#404a41","900":"#222823"},"warning":{"50":"#ece7de","100":"#f5e0b5","200":"#f6d289","300":"#f9c962","400":"#f7bf3a","500":"#eeb308","600":"#c38e00","700":"#845a00","800":"#584720","900":"#332400"},"warning-muted":{"50":"#e8e8e6","100":"#e7e4dc","200":"#e2dcd0","300":"#ded7c8","400":"#dad1c0","500":"#d2c9b8","600":"#a9a090","700":"#6d6659","800":"#504c42","900":"#292621"},"error":{"50":"#f3e4e2","100":"#ffc8c1","200":"#ffa198","300":"#ff847b","400":"#ff6560","500":"#ef4444","600":"#cb2d31","700":"#950e18","800":"#5a312e","900":"#3f1917"},"error-muted":{"50":"#e9e7e7","100":"#e6dad8","200":"#d6c3c1","300":"#cdb4b2","400":"#bfa4a1","500":"#ab908d","600":"#8d7572","700":"#624e4c","800":"#4c3d3b","900":"#2d2423"},"info":{"50":"#e1e9ef","100":"#bce4fe","200":"#8fd2fd","300":"#6ac7ff","400":"#43b9fa","500":"#0ea5e9","600":"#0087c4","700":"#005c8d","800":"#1e465d","900":"#082a3c"},"info-muted":{"50":"#e7e8e9","100":"#d9e0e5","200":"#c5d0d7","300":"#b8c6cf","400":"#aab9c4","500":"#99a9b3","600":"#7a8993","700":"#4f5b63","800":"#3d464c","900":"#22272a"},"neutral":{"50":"#fafafa","100":"#f3f3f3","200":"#e4e4e4","300":"#cacaca","400":"#ababab","500":"#868686","600":"#696969","700":"#4d4d4d","800":"#333333","900":"#1b1b1b"},"white":"#FFFFFF","black":"#000000"},"spacing":{"px":{"0":"0px","1":"4px","2":"8px","3":"12px","4":"16px","5":"20px","6":"24px","7":"28px","8":"32px","9":"36px","10":"40px","11":"44px","12":"48px","13":"52px","14":"56px","15":"60px","16":"64px","17":"68px","18":"72px","19":"76px","20":"80px","21":"84px","22":"88px","23":"92px","24":"96px"},"rem":{"0":"0rem","1":"0.25rem","2":"0.5rem","3":"0.75rem","4":"1rem","5":"1.25rem","6":"1.5rem","7":"1.75rem","8":"2rem","9":"2.25rem","10":"2.5rem","11":"2.75rem","12":"3rem","13":"3.25rem","14":"3.5rem","15":"3.75rem","16":"4rem","17":"4.25rem","18":"4.5rem","19":"4.75rem","20":"5rem","21":"5.25rem","22":"5.5rem","23":"5.75rem","24":"6rem"}},"border":{"radius":"0.5rem","width":"1px"},"typography":{"fontFamilyPrimary":"\"Hanken Grotesk\", sans-serif"},"elevation":{"0":"none","1":"0 1px 3px 0 rgb(27 27 27 / 0.1), 0 1px 2px 0 rgb(27 27 27 / 0.06)","2":"0 4px 6px -1px rgb(27 27 27 / 0.1), 0 2px 4px -1px rgb(27 27 27 / 0.06)","3":"0 10px 15px -3px rgb(27 27 27 / 0.1), 0 4px 6px -2px rgb(27 27 27 / 0.05)","4":"0 20px 25px -5px rgb(27 27 27 / 0.1), 0 10px 10px -5px rgb(27 27 27 / 0.04)","5":"0 25px 50px -12px rgb(27 27 27 / 0.25)"},"semantic":{"text-default":"color-neutral-900","text-subtle":"color-neutral-600","text-muted":"color-neutral-400","text-disabled":"color-neutral-400","text-inverse":"color-white","text-neutral":"color-neutral-700","text-primary":"color-primary-700","text-on-primary":"color-neutral-900","text-secondary":"color-secondary-700","text-on-secondary":"color-white","text-success":"color-success-700","text-on-success":"color-white","text-warning":"color-warning-700","text-on-warning":"color-warning-800","text-error":"color-error-700","text-on-error":"color-white","text-info":"color-info-700","text-on-info":"color-white","bg-default":"color-white","bg-subtle":"color-neutral-50","bg-surface":"color-neutral-100","bg-overlay":"color-neutral-200","bg-inverse":"color-neutral-900","bg-neutral-subtle":"color-neutral-100","bg-primary":"color-primary-500","bg-primary-subtle":"color-primary-100","bg-secondary":"color-secondary-500","bg-secondary-subtle":"color-secondary-100","bg-success":"color-success-500","bg-success-subtle":"color-success-100","bg-warning":"color-warning-500","bg-warning-subtle":"color-warning-100","bg-error":"color-error-500","bg-error-subtle":"color-error-100","bg-info":"color-info-500","bg-info-subtle":"color-info-100","border-default":"color-neutral-200","border-strong":"color-neutral-400","border-subtle":"color-neutral-100","border-focus":"color-primary-200","border-primary":"color-primary-200","border-primary-strong":"color-primary-500","border-secondary":"color-secondary-200","border-secondary-strong":"color-secondary-500","border-success":"color-success-200","border-success-strong":"color-success-500","border-warning":"color-warning-200","border-warning-strong":"color-warning-500","border-error":"color-error-200","border-error-strong":"color-error-500","border-info":"color-info-200","border-info-strong":"color-info-500","interactive-primary-bg":"color-primary-500","interactive-primary-bg-hover":"color-primary-600","interactive-primary-bg-active":"color-primary-700","interactive-secondary-bg":"color-secondary-500","interactive-secondary-bg-hover":"color-secondary-600","interactive-secondary-bg-active":"color-secondary-700","interactive-success-bg":"color-success-500","interactive-success-bg-hover":"color-success-600","interactive-success-bg-active":"color-success-700","interactive-warning-bg":"color-warning-500","interactive-warning-bg-hover":"color-warning-600","interactive-warning-bg-active":"color-warning-700","interactive-error-bg":"color-error-500","interactive-error-bg-hover":"color-error-600","interactive-error-bg-active":"color-error-700","interactive-info-bg":"color-info-500","interactive-info-bg-hover":"color-info-600","interactive-info-bg-active":"color-info-700"},"semanticDark":{"text-default":"color-neutral-100","text-subtle":"color-neutral-300","text-muted":"color-neutral-500","text-disabled":"color-neutral-600","text-inverse":"color-neutral-900","text-neutral":"color-neutral-200","text-primary":"color-primary-300","text-on-primary":"color-neutral-900","text-secondary":"color-secondary-300","text-on-secondary":"color-white","text-success":"color-success-300","text-on-success":"color-white","text-warning":"color-warning-300","text-on-warning":"color-warning-800","text-error":"color-error-300","text-on-error":"color-white","text-info":"color-info-300","text-on-info":"color-white","bg-default":"color-neutral-900","bg-subtle":"color-neutral-800","bg-surface":"color-neutral-800","bg-overlay":"color-neutral-700","bg-inverse":"color-neutral-50","bg-neutral-subtle":"color-neutral-800","bg-primary":"color-primary-500","bg-primary-subtle":"color-primary-900","bg-secondary":"color-secondary-500","bg-secondary-subtle":"color-secondary-900","bg-success":"color-success-500","bg-success-subtle":"color-success-900","bg-warning":"color-warning-500","bg-warning-subtle":"color-warning-900","bg-error":"color-error-500","bg-error-subtle":"color-error-900","bg-info":"color-info-500","bg-info-subtle":"color-info-900","border-default":"color-neutral-700","border-strong":"color-neutral-500","border-subtle":"color-neutral-800","border-focus":"color-primary-400","border-primary":"color-primary-700","border-primary-strong":"color-primary-500","border-secondary":"color-secondary-700","border-secondary-strong":"color-secondary-500","border-success":"color-success-700","border-success-strong":"color-success-500","border-warning":"color-warning-700","border-warning-strong":"color-warning-500","border-error":"color-error-700","border-error-strong":"color-error-500","border-info":"color-info-700","border-info-strong":"color-info-500","interactive-primary-bg":"color-primary-400","interactive-primary-bg-hover":"color-primary-300","interactive-primary-bg-active":"color-primary-500","interactive-secondary-bg":"color-secondary-400","interactive-secondary-bg-hover":"color-secondary-300","interactive-secondary-bg-active":"color-secondary-500","interactive-success-bg":"color-success-400","interactive-success-bg-hover":"color-success-300","interactive-success-bg-active":"color-success-500","interactive-warning-bg":"color-warning-400","interactive-warning-bg-hover":"color-warning-300","interactive-warning-bg-active":"color-warning-500","interactive-error-bg":"color-error-400","interactive-error-bg-hover":"color-error-300","interactive-error-bg-active":"color-error-500","interactive-info-bg":"color-info-400","interactive-info-bg-hover":"color-info-300","interactive-info-bg-active":"color-info-500"}};
package/dist/tokens.json CHANGED
@@ -220,7 +220,7 @@
220
220
  "width": "1px"
221
221
  },
222
222
  "typography": {
223
- "fontFamily": "\"Hanken Grotesk\", sans-serif"
223
+ "fontFamilyPrimary": "\"Hanken Grotesk\", sans-serif"
224
224
  },
225
225
  "elevation": {
226
226
  "0": "none",
@@ -24,7 +24,7 @@ import type { ElevationTokens } from '../elevation/';
24
24
  * radius: "0.375rem"
25
25
  * },
26
26
  * typography: {
27
- * fontFamily: "Hanken Grotesk, sans-serif"
27
+ * fontFamilyPrimary: "Hanken Grotesk, sans-serif"
28
28
  * },
29
29
  * elevation: {
30
30
  * "0": "none",
@@ -42,8 +42,8 @@ export type Tokens = {
42
42
  width: string;
43
43
  };
44
44
  typography: {
45
- fontFamily: string;
46
- };
45
+ fontFamilyPrimary: string;
46
+ } & Record<string, string>;
47
47
  elevation: ElevationTokens;
48
48
  };
49
49
  export type { Config, TokenColorName } from './';
@@ -53,22 +53,48 @@ export type { Config, TokenColorName } from './';
53
53
  * Any other CSS length (e.g. `'1.5px'`) passes through unchanged.
54
54
  */
55
55
  export type BorderWidth = 'thin' | 'medium' | 'thick' | (string & {});
56
+ /** Generic CSS font-family keyword used as the safety-net fallback if a CDN font fails to load. */
57
+ export type FontFallback = 'sans-serif' | 'serif' | 'monospace' | 'cursive' | 'fantasy' | 'system-ui';
58
+ /**
59
+ * A single `font.family` entry: a plain CSS value, a Google Fonts CSS2 URL, or
60
+ * a URL paired with a custom generic fallback (default `'sans-serif'`) for
61
+ * when the derived family isn't sans-serif — e.g. a serif display font.
62
+ */
63
+ export type FontFamilyEntry = string | {
64
+ url: string;
65
+ fallback?: FontFallback;
66
+ };
56
67
  export interface LattyConfig {
57
68
  /** Built-in names: primary, secondary, success, warning, error, info. Any additional name generates a new palette. */
58
69
  colors?: Record<string, string>;
59
70
  font?: {
60
71
  /**
61
- * A CSS `font-family` value (e.g. `'Inter, sans-serif'`), or a Google
62
- * Fonts CSS2 stylesheet URL (e.g.
63
- * `'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900&display=swap'`).
64
- * URLs get `@import`ed automatically and the family name is derived from
65
- * the `family=` query param — no separate `<link>` tag needed. Stylesheet
66
- * URLs from providers other than `fonts.googleapis.com` are still loaded,
67
- * but since there's no reliable way to guess the family name from an
68
- * arbitrary CDN, pass the resulting name as a plain value instead (or
69
- * open an issue if there's a provider worth special-casing).
72
+ * A CSS `font-family` value (e.g. `'Inter, sans-serif'`), a Google Fonts
73
+ * CSS2 stylesheet URL (e.g.
74
+ * `'https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900&display=swap'`),
75
+ * a `{ url, fallback }` object, or an array of any of these. URLs get
76
+ * `@import`ed automatically and the family name is derived from the
77
+ * `family=` query param — no separate `<link>` tag needed. Stylesheet
78
+ * URLs from providers other than `fonts.googleapis.com` are still
79
+ * loaded, but since there's no reliable way to guess the family name
80
+ * from an arbitrary CDN, pass the resulting name as a plain value
81
+ * instead (or open an issue if there's a provider worth special-casing).
82
+ *
83
+ * A bare URL string always falls back to the generic `sans-serif` family
84
+ * if the font itself fails to load. Use the `{ url, fallback }` object
85
+ * form to pick a different generic fallback (e.g. `'serif'` for a serif
86
+ * display font):
87
+ * ```ts
88
+ * family: { url: 'https://fonts.googleapis.com/css2?family=Playfair+Display', fallback: 'serif' }
89
+ * ```
90
+ *
91
+ * When an array is passed, each entry maps to its own token in order —
92
+ * `--lt-typography-fontFamilyPrimary`, `...Secondary`, `...Tertiary`,
93
+ * etc. — rather than being combined into a single CSS fallback stack, so
94
+ * multiple fonts (e.g. a heading font and a body font) can each be
95
+ * referenced independently in your own CSS.
70
96
  */
71
- family?: string;
97
+ family?: FontFamilyEntry | FontFamilyEntry[];
72
98
  };
73
99
  border?: {
74
100
  radius?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latty-ds/tokens",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Design tokens for the Latty design system — generates CSS custom properties, JSON, and JavaScript exports from a single config file",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -60,5 +60,5 @@
60
60
  "dependencies": {
61
61
  "culori": "^4.0.2"
62
62
  },
63
- "gitHead": "144dd8d2f7440fd5fd8ecb4f396407c4ce3be9f2"
63
+ "gitHead": "8d5627a4a71bb4c7efdf3b754198c6450ce5467f"
64
64
  }