@ticketlayer/theme 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ticketlayer Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # @ticketlayer/theme
2
+
3
+ The Ticketlayer Live design tokens. One source, every harness.
4
+
5
+ `tokens.json` is that source. Every `--tl-*` custom property the Elements read,
6
+ the typed object a native harness will render from, the JSON schema the Backstage
7
+ theme editor and the sales-channel theme API validate against, and `TOKENS.md`
8
+ are generated from it. A token's default value is written down once, in
9
+ `tokens.json`, and nowhere else.
10
+
11
+ ```bash
12
+ pnpm --filter @ticketlayer/theme run generate # rewrite everything generated
13
+ pnpm --filter @ticketlayer/theme run lint # fail if what is committed is not that
14
+ ```
15
+
16
+ ## What comes out
17
+
18
+ | Artifact | For |
19
+ |---|---|
20
+ | `generated/tokens.css` | the web defaults, one `:root` block. `@ticketlayer/elements` generates its Stencil global stylesheet from this, so the Elements ship it |
21
+ | `generated/tokens.dark.css` | the dark values, scoped to `[data-tl-color-scheme='dark']`. Opt in: nothing loads it today |
22
+ | `src/generated/tokens.ts` | `TOKEN_NAMES`, `TOKEN_DEFAULTS`, `TOKEN_THEME_KEYS`, and `lightTheme` / `darkTheme`, the typed grouped objects a React Native harness renders from |
23
+ | `generated/theme.schema.json` | the theme document: what an operator edits and what `GET /sales/theme` returns |
24
+ | `TOKENS.md` | the docs page, one table per group |
25
+
26
+ ## Using it
27
+
28
+ ```ts
29
+ import { lightTheme, darkTheme, TOKEN_DEFAULTS, themeToCssVars } from '@ticketlayer/theme';
30
+ import '@ticketlayer/theme/tokens.css'; // only if you are not loading the Elements
31
+ ```
32
+
33
+ `themeToCssVars(theme)` turns a channel theme from `GET /sales/theme` into the
34
+ custom properties that override the defaults, including every alias a token
35
+ answers to. `@ticketlayer/live`'s `applyTheme` is the web harness that writes
36
+ them onto `:root`; there is no second injection mechanism, and a harness that
37
+ needs one calls this function rather than writing its own mapping.
38
+
39
+ ## The two rules
40
+
41
+ **A token missing on one runtime fails CI.** Every token in `tokens.json` is
42
+ declared for every runtime unless it names a shorter `runtimes` list, and a
43
+ shorter list has to carry a `runtimesNote` saying why. The web stylesheet and
44
+ the native object are generated from that one list and compared after they are
45
+ rendered, so the harnesses cannot drift onto different token names without
46
+ somebody writing down that they are meant to.
47
+
48
+ **A `var(--tl-*)` that is not a declared token fails CI.** The Elements carry no
49
+ per-use-site fallbacks: `var(--tl-primary, #6366f1)` was a second place the
50
+ default lived, and the second places had drifted to three different values for
51
+ that token alone. `packages/elements/scripts/check-token-usage.mjs` reads this
52
+ package's token list and fails on a `var(--tl-*)` naming something undeclared,
53
+ or carrying a fallback at all. Rename a token and every use site fails.
54
+
55
+ ## Aliases
56
+
57
+ A few tokens answer to more than one name, because more than one name is already
58
+ in the wild. `--tl-color-primary` beside `--tl-primary` (the Elements use both),
59
+ `--tl-destructive` beside `--tl-error` (the API calls it one, the Elements the
60
+ other), `--tl-base-size` beside `--tl-font-size-base` (the Backstage theme
61
+ designer emits one, this client the other), and a bare `--tl-radius` for
62
+ `--tl-radius-md`. Aliases are declared in `tokens.json`, emitted into the
63
+ stylesheet as `--tl-color-primary: var(--tl-primary)`, and written out by
64
+ `themeToCssVars` so a channel theme reaches all of them. New stylesheets use
65
+ the token name, not an alias.
package/TOKENS.md ADDED
@@ -0,0 +1,99 @@
1
+ <!-- Generated from tokens.json by packages/theme/scripts/generate.mjs. Do not edit. -->
2
+
3
+ # Ticketlayer design tokens
4
+
5
+ 44 tokens, in 5 groups. Every one of them is
6
+ declared in `tokens.json` and generated from there into the web custom properties,
7
+ the Stencil global stylesheet the Elements load, the typed object a native harness
8
+ renders from, the JSON schema the theme editor and the sales-channel theme API
9
+ validate against, and this page.
10
+
11
+ A token that carries a **theme key** can be set per sales channel: that key is what
12
+ `GET /sales/theme` returns and what `@ticketlayer/live` writes onto `:root` at runtime.
13
+ A token without one is a constant of the design system and can only be changed here.
14
+
15
+ ## Colour
16
+
17
+ Palette. A channel theme overrides these through colours on GET /sales/theme.
18
+
19
+ | Custom property | Default | Dark | Theme key | Also answers to |
20
+ |---|---|---|---|---|
21
+ | `--tl-primary` | `#6366f1` | `#818cf8` | `colors.primary` | `--tl-color-primary` |
22
+ | `--tl-primary-foreground` | `#ffffff` | `#111827` | `colors.primaryForeground` | `--tl-color-primary-foreground` |
23
+ | `--tl-secondary` | `#f3f4f6` | `#1f2937` | `colors.secondary` | `--tl-color-secondary` |
24
+ | `--tl-secondary-foreground` | `#111827` | `#f9fafb` | `colors.secondaryForeground` | `--tl-color-secondary-foreground` |
25
+ | `--tl-accent` | `#ec4899` | `#f472b6` | `colors.accent` | `--tl-color-accent` |
26
+ | `--tl-accent-foreground` | `#ffffff` | `#111827` | `colors.accentForeground` | `--tl-color-accent-foreground` |
27
+ | `--tl-background` | `#ffffff` | `#0b0f19` | `colors.background` | `--tl-color-background` |
28
+ | `--tl-foreground` | `#111827` | `#f9fafb` | `colors.foreground` | `--tl-color-foreground` |
29
+ | `--tl-card` | `#ffffff` | `#111827` | - | `--tl-color-card` |
30
+ | `--tl-muted` | `#f9fafb` | `#111827` | `colors.muted` | `--tl-color-muted` |
31
+ | `--tl-muted-foreground` | `#6b7280` | `#9ca3af` | `colors.mutedForeground` | `--tl-color-muted-foreground` |
32
+ | `--tl-border` | `#e5e7eb` | `#374151` | `colors.border` | `--tl-color-border` |
33
+ | `--tl-border-hover` | `#9ca3af` | `#4b5563` | - | `--tl-color-border-hover` |
34
+ | `--tl-input` | `#e5e7eb` | `#374151` | `colors.input` | `--tl-color-input` |
35
+ | `--tl-ring` | `#6366f1` | `#818cf8` | `colors.ring` | `--tl-color-ring` |
36
+ | `--tl-success` | `#10b981` | `#34d399` | `colors.success` | `--tl-color-success` |
37
+ | `--tl-success-foreground` | `#ffffff` | `#062a1d` | `colors.successForeground` | `--tl-color-success-foreground` |
38
+ | `--tl-warning` | `#f59e0b` | `#fbbf24` | `colors.warning` | `--tl-color-warning` |
39
+ | `--tl-warning-foreground` | `#ffffff` | `#2a1a02` | `colors.warningForeground` | `--tl-color-warning-foreground` |
40
+ | `--tl-warning-bg` | `#fef3c7` | `#422006` | - | `--tl-color-warning-bg` |
41
+ | `--tl-error` | `#ef4444` | `#f87171` | `colors.destructive` | `--tl-color-error`, `--tl-destructive`, `--tl-color-destructive` |
42
+ | `--tl-error-foreground` | `#ffffff` | `#2a0a0a` | `colors.destructiveForeground` | `--tl-color-error-foreground`, `--tl-destructive-foreground`, `--tl-color-destructive-foreground` |
43
+ | `--tl-error-bg` | `#fee2e2` | `#450a0a` | - | `--tl-color-error-bg`, `--tl-destructive-bg`, `--tl-color-destructive-bg` |
44
+
45
+ ## Type
46
+
47
+ Font stacks and the base size the type scale is built from.
48
+
49
+ | Custom property | Default | Dark | Theme key | Also answers to |
50
+ |---|---|---|---|---|
51
+ | `--tl-font-family` | `system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif` | - | `typography.fontFamily` | - |
52
+ | `--tl-font-family-mono` | `ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace` | - | `typography.fontFamilyMono` | - |
53
+ | `--tl-font-size-base` | `16px` | - | `typography.baseSize` | `--tl-base-size` |
54
+ | `--tl-font-scale` | `1.25` | - | `typography.scaleRatio` | - |
55
+
56
+ ## Radius
57
+
58
+ Corner radii. A channel theme overrides these through shape.
59
+
60
+ | Custom property | Default | Dark | Theme key | Also answers to |
61
+ |---|---|---|---|---|
62
+ | `--tl-radius-none` | `0px` | - | `shape.radiusNone` | - |
63
+ | `--tl-radius-sm` | `0.25rem` | - | `shape.radiusSm` | - |
64
+ | `--tl-radius-md` | `0.5rem` | - | `shape.radiusMd` | `--tl-radius` |
65
+ | `--tl-radius-lg` | `0.75rem` | - | `shape.radiusLg` | - |
66
+ | `--tl-radius-full` | `9999px` | - | `shape.radiusFull` | - |
67
+
68
+ ## Spacing
69
+
70
+ The spacing step scale. Not themeable through the API today.
71
+
72
+ | Custom property | Default | Dark | Theme key | Also answers to |
73
+ |---|---|---|---|---|
74
+ | `--tl-spacing-1` | `0.25rem` | - | - | - |
75
+ | `--tl-spacing-2` | `0.5rem` | - | - | - |
76
+ | `--tl-spacing-3` | `0.75rem` | - | - | - |
77
+ | `--tl-spacing-4` | `1rem` | - | - | - |
78
+ | `--tl-spacing-5` | `1.25rem` | - | - | - |
79
+ | `--tl-spacing-6` | `1.5rem` | - | - | - |
80
+ | `--tl-spacing-8` | `2rem` | - | - | - |
81
+
82
+ ## Motion
83
+
84
+ Durations and easings. Not themeable through the API today.
85
+
86
+ | Custom property | Default | Dark | Theme key | Also answers to |
87
+ |---|---|---|---|---|
88
+ | `--tl-duration-fast` | `120ms` | - | - | - |
89
+ | `--tl-duration-normal` | `200ms` | - | - | - |
90
+ | `--tl-duration-slow` | `320ms` | - | - | - |
91
+ | `--tl-easing-standard` | `cubic-bezier(0.2, 0, 0, 1)` | - | - | - |
92
+ | `--tl-easing-emphasised` | `cubic-bezier(0.3, 0, 0, 1)` | - | - | - |
93
+
94
+ ## Runtimes
95
+
96
+ Every token above exists on every runtime. A token may name a shorter `runtimes`
97
+ list in `tokens.json`, but only with a `runtimesNote` saying why, and
98
+ `pnpm lint` fails without one: that is what stops the web and native harnesses
99
+ drifting onto different token names.
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Typed access to the token set. Generated from packages/theme/tokens.json;
3
+ * change a token there and regenerate.
4
+ */
5
+ /** Every token name, in declaration order. */
6
+ export declare const TOKEN_NAMES: readonly ["primary", "primary-foreground", "secondary", "secondary-foreground", "accent", "accent-foreground", "background", "foreground", "card", "muted", "muted-foreground", "border", "border-hover", "input", "ring", "success", "success-foreground", "warning", "warning-foreground", "warning-bg", "error", "error-foreground", "error-bg", "font-family", "font-family-mono", "font-size-base", "font-scale", "radius-none", "radius-sm", "radius-md", "radius-lg", "radius-full", "spacing-1", "spacing-2", "spacing-3", "spacing-4", "spacing-5", "spacing-6", "spacing-8", "duration-fast", "duration-normal", "duration-slow", "easing-standard", "easing-emphasised"];
7
+ /** A token name. Renaming a token in tokens.json changes this union, and every consumer that named the old one stops compiling. */
8
+ export type TokenName = (typeof TOKEN_NAMES)[number];
9
+ /** Alias name -> the token it resolves to. */
10
+ export declare const TOKEN_ALIASES: {
11
+ readonly 'color-primary': "primary";
12
+ readonly 'color-primary-foreground': "primary-foreground";
13
+ readonly 'color-secondary': "secondary";
14
+ readonly 'color-secondary-foreground': "secondary-foreground";
15
+ readonly 'color-accent': "accent";
16
+ readonly 'color-accent-foreground': "accent-foreground";
17
+ readonly 'color-background': "background";
18
+ readonly 'color-foreground': "foreground";
19
+ readonly 'color-card': "card";
20
+ readonly 'color-muted': "muted";
21
+ readonly 'color-muted-foreground': "muted-foreground";
22
+ readonly 'color-border': "border";
23
+ readonly 'color-border-hover': "border-hover";
24
+ readonly 'color-input': "input";
25
+ readonly 'color-ring': "ring";
26
+ readonly 'color-success': "success";
27
+ readonly 'color-success-foreground': "success-foreground";
28
+ readonly 'color-warning': "warning";
29
+ readonly 'color-warning-foreground': "warning-foreground";
30
+ readonly 'color-warning-bg': "warning-bg";
31
+ readonly 'color-error': "error";
32
+ readonly destructive: "error";
33
+ readonly 'color-destructive': "error";
34
+ readonly 'color-error-foreground': "error-foreground";
35
+ readonly 'destructive-foreground': "error-foreground";
36
+ readonly 'color-destructive-foreground': "error-foreground";
37
+ readonly 'color-error-bg': "error-bg";
38
+ readonly 'destructive-bg': "error-bg";
39
+ readonly 'color-destructive-bg': "error-bg";
40
+ readonly 'base-size': "font-size-base";
41
+ readonly radius: "radius-md";
42
+ };
43
+ export type TokenAlias = keyof typeof TOKEN_ALIASES;
44
+ /** The default (light) value of every token, by token name. */
45
+ export declare const TOKEN_DEFAULTS: Record<TokenName, string>;
46
+ /** The dark value of every token that has one. */
47
+ export declare const TOKEN_DARK: Partial<Record<TokenName, string>>;
48
+ /** The theme-document key that feeds each token, for the tokens the theme API can set. */
49
+ export declare const TOKEN_THEME_KEYS: Partial<Record<TokenName, string>>;
50
+ /** The group each token belongs to. */
51
+ export declare const TOKEN_GROUPS: Record<TokenName, string>;
52
+ /**
53
+ * The typed theme object a native harness renders from: the same tokens, grouped,
54
+ * with camelCase keys and no CSS anywhere near them.
55
+ */
56
+ export declare const lightTheme: {
57
+ readonly color: {
58
+ readonly primary: "#6366f1";
59
+ readonly primaryForeground: "#ffffff";
60
+ readonly secondary: "#f3f4f6";
61
+ readonly secondaryForeground: "#111827";
62
+ readonly accent: "#ec4899";
63
+ readonly accentForeground: "#ffffff";
64
+ readonly background: "#ffffff";
65
+ readonly foreground: "#111827";
66
+ readonly card: "#ffffff";
67
+ readonly muted: "#f9fafb";
68
+ readonly mutedForeground: "#6b7280";
69
+ readonly border: "#e5e7eb";
70
+ readonly borderHover: "#9ca3af";
71
+ readonly input: "#e5e7eb";
72
+ readonly ring: "#6366f1";
73
+ readonly success: "#10b981";
74
+ readonly successForeground: "#ffffff";
75
+ readonly warning: "#f59e0b";
76
+ readonly warningForeground: "#ffffff";
77
+ readonly warningBg: "#fef3c7";
78
+ readonly error: "#ef4444";
79
+ readonly errorForeground: "#ffffff";
80
+ readonly errorBg: "#fee2e2";
81
+ };
82
+ readonly typography: {
83
+ readonly fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
84
+ readonly fontFamilyMono: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace";
85
+ readonly fontSizeBase: "16px";
86
+ readonly fontScale: "1.25";
87
+ };
88
+ readonly radius: {
89
+ readonly none: "0px";
90
+ readonly sm: "0.25rem";
91
+ readonly md: "0.5rem";
92
+ readonly lg: "0.75rem";
93
+ readonly full: "9999px";
94
+ };
95
+ readonly spacing: {
96
+ readonly '1': "0.25rem";
97
+ readonly '2': "0.5rem";
98
+ readonly '3': "0.75rem";
99
+ readonly '4': "1rem";
100
+ readonly '5': "1.25rem";
101
+ readonly '6': "1.5rem";
102
+ readonly '8': "2rem";
103
+ };
104
+ readonly motion: {
105
+ readonly durationFast: "120ms";
106
+ readonly durationNormal: "200ms";
107
+ readonly durationSlow: "320ms";
108
+ readonly easingStandard: "cubic-bezier(0.2, 0, 0, 1)";
109
+ readonly easingEmphasised: "cubic-bezier(0.3, 0, 0, 1)";
110
+ };
111
+ };
112
+ export declare const darkTheme: {
113
+ readonly color: {
114
+ readonly primary: "#818cf8";
115
+ readonly primaryForeground: "#111827";
116
+ readonly secondary: "#1f2937";
117
+ readonly secondaryForeground: "#f9fafb";
118
+ readonly accent: "#f472b6";
119
+ readonly accentForeground: "#111827";
120
+ readonly background: "#0b0f19";
121
+ readonly foreground: "#f9fafb";
122
+ readonly card: "#111827";
123
+ readonly muted: "#111827";
124
+ readonly mutedForeground: "#9ca3af";
125
+ readonly border: "#374151";
126
+ readonly borderHover: "#4b5563";
127
+ readonly input: "#374151";
128
+ readonly ring: "#818cf8";
129
+ readonly success: "#34d399";
130
+ readonly successForeground: "#062a1d";
131
+ readonly warning: "#fbbf24";
132
+ readonly warningForeground: "#2a1a02";
133
+ readonly warningBg: "#422006";
134
+ readonly error: "#f87171";
135
+ readonly errorForeground: "#2a0a0a";
136
+ readonly errorBg: "#450a0a";
137
+ };
138
+ readonly typography: {
139
+ readonly fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
140
+ readonly fontFamilyMono: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace";
141
+ readonly fontSizeBase: "16px";
142
+ readonly fontScale: "1.25";
143
+ };
144
+ readonly radius: {
145
+ readonly none: "0px";
146
+ readonly sm: "0.25rem";
147
+ readonly md: "0.5rem";
148
+ readonly lg: "0.75rem";
149
+ readonly full: "9999px";
150
+ };
151
+ readonly spacing: {
152
+ readonly '1': "0.25rem";
153
+ readonly '2': "0.5rem";
154
+ readonly '3': "0.75rem";
155
+ readonly '4': "1rem";
156
+ readonly '5': "1.25rem";
157
+ readonly '6': "1.5rem";
158
+ readonly '8': "2rem";
159
+ };
160
+ readonly motion: {
161
+ readonly durationFast: "120ms";
162
+ readonly durationNormal: "200ms";
163
+ readonly durationSlow: "320ms";
164
+ readonly easingStandard: "cubic-bezier(0.2, 0, 0, 1)";
165
+ readonly easingEmphasised: "cubic-bezier(0.3, 0, 0, 1)";
166
+ };
167
+ };
168
+ export type NativeTheme = typeof lightTheme;
169
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/generated/tokens.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,8CAA8C;AAC9C,eAAO,MAAM,WAAW,qpBA6Cd,CAAC;AAEX,mIAAmI;AACnI,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,8CAA8C;AAC9C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgChB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,aAAa,CAAC;AAEpD,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CA6CpD,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAwBzD,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CA6B/D,CAAC;AAEF,uCAAuC;AACvC,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CA6ClD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDb,CAAC;AAEX,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDZ,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,OAAO,UAAU,CAAC"}