@homepages/template-kit 0.1.0 → 0.3.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +70 -16
  3. package/dist/base.css +36 -48
  4. package/dist/cli/check/config.js +37 -0
  5. package/dist/cli/check/css.js +117 -0
  6. package/dist/cli/check/diagnostics.js +32 -0
  7. package/dist/cli/check/index.js +87 -0
  8. package/dist/cli/check/loader.js +142 -0
  9. package/dist/cli/check/paths.js +15 -0
  10. package/dist/cli/check/relativize.js +18 -0
  11. package/dist/cli/check/render-invariants.js +24 -0
  12. package/dist/cli/check/resolve-tool.js +38 -0
  13. package/dist/cli/check/stages/deps.js +337 -0
  14. package/dist/cli/check/stages/lint.js +46 -0
  15. package/dist/cli/check/stages/render.js +101 -0
  16. package/dist/cli/check/stages/size.js +158 -0
  17. package/dist/cli/check/stages/tree.js +207 -0
  18. package/dist/cli/check/stages/typecheck.js +46 -0
  19. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +18 -0
  20. package/dist/cli/check/stages/validate/facts.js +18 -0
  21. package/dist/cli/check/stages/validate/formatter-contract.js +14 -0
  22. package/dist/cli/check/stages/validate/group-contract.js +21 -0
  23. package/dist/cli/check/stages/validate/index.js +66 -0
  24. package/dist/cli/check/stages/validate/list-scalar-contract.js +22 -0
  25. package/dist/cli/check/stages/validate/nav-contract.js +93 -0
  26. package/dist/cli/check/stages/validate/nested-slot-contract.js +12 -0
  27. package/dist/cli/check/stages/validate/object-list-contract.js +31 -0
  28. package/dist/cli/check/stages/validate/orchestrator.js +241 -0
  29. package/dist/cli/check/stages/validate/produced-by-contract.js +18 -0
  30. package/dist/cli/check/stages/validate/row-contract.js +31 -0
  31. package/dist/cli/check/stages/validate/select-contract.js +13 -0
  32. package/dist/cli/check/stages/validate/source-contract.js +21 -0
  33. package/dist/cli/check/stages/validate/variant-contract.js +15 -0
  34. package/dist/cli/check/workspace.js +86 -0
  35. package/dist/cli/theme/generate.js +43 -0
  36. package/dist/cli/theme/index.js +33 -0
  37. package/dist/cli/theme/load-theme.js +69 -0
  38. package/dist/cli.js +22 -4
  39. package/dist/contracts/fill-treatments.js +47 -0
  40. package/dist/design-system/theme.d.ts +30 -299
  41. package/dist/design-system/theme.js +112 -83
  42. package/dist/eslint/rules/image-bare-needs-reason.js +6 -1
  43. package/dist/eslint/rules/no-client-directive-in-contract.js +6 -1
  44. package/dist/eslint/rules/no-client-runtime-in-server.js +5 -1
  45. package/dist/eslint/rules/no-css-import-from-render-path.js +8 -2
  46. package/dist/eslint/rules/no-hex.js +84 -7
  47. package/dist/eslint/rules/no-inline-style.js +5 -1
  48. package/dist/eslint/rules/no-nondeterminism.js +5 -1
  49. package/dist/eslint/rules/no-raw-element.js +6 -1
  50. package/dist/eslint/rules/props-from-schema.js +6 -1
  51. package/dist/eslint/rules/serializable-island-props.js +5 -1
  52. package/dist/eslint/rules/slot-marker-literal.js +6 -1
  53. package/dist/eslint.js +1 -1
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.js +2 -2
  56. package/dist/package.js +1 -1
  57. package/dist/primitives/Image.js +1 -1
  58. package/dist/rules/registry.js +16 -0
  59. package/dist/schema/fill-spec.d.ts +80 -590
  60. package/dist/schema/fixture-schema.d.ts +5 -81
  61. package/dist/schema/manifest.d.ts +39 -475
  62. package/dist/schema/resolve-section-ref.js +10 -0
  63. package/dist/schema/rows.js +10 -0
  64. package/dist/schema/section-nav.js +9 -0
  65. package/dist/schema/section-schema.d.ts +51 -437
  66. package/dist/schema/slot-types.d.ts +12 -16
  67. package/dist/styles.css +31 -88
  68. package/docs/INDEX.md +5 -3
  69. package/docs/check.md +121 -0
  70. package/docs/eslint.md +25 -50
  71. package/docs/llms.txt +26 -11
  72. package/docs/primitives.md +5 -0
  73. package/docs/rules/INDEX.md +65 -0
  74. package/docs/rules/audit-severity.md +91 -0
  75. package/docs/rules/bundle-binary-asset.md +102 -0
  76. package/docs/rules/bundle-incomplete.md +117 -0
  77. package/docs/rules/css-reason.md +94 -0
  78. package/docs/rules/determinism-drift.md +112 -0
  79. package/docs/rules/fixtures-invalid.md +96 -0
  80. package/docs/rules/image-bare-needs-reason.md +117 -0
  81. package/docs/rules/license-denied.md +114 -0
  82. package/docs/rules/lockfile-missing.md +68 -0
  83. package/docs/rules/lockfile-stale.md +88 -0
  84. package/docs/rules/manifest-invalid.md +99 -0
  85. package/docs/rules/missing-slot-marker.md +140 -0
  86. package/docs/rules/no-bare-css-import.md +128 -0
  87. package/docs/rules/no-client-directive-in-contract.md +111 -0
  88. package/docs/rules/no-client-runtime-in-server.md +143 -0
  89. package/docs/rules/no-css-import-from-render-path.md +118 -0
  90. package/docs/rules/no-hex.md +163 -0
  91. package/docs/rules/no-inline-style.md +121 -0
  92. package/docs/rules/no-nondeterminism.md +100 -0
  93. package/docs/rules/no-raw-element.md +100 -0
  94. package/docs/rules/no-templates.md +87 -0
  95. package/docs/rules/parse-error.md +76 -0
  96. package/docs/rules/props-from-schema.md +119 -0
  97. package/docs/rules/render-invariant.md +165 -0
  98. package/docs/rules/schema-invalid.md +96 -0
  99. package/docs/rules/serializable-island-props.md +146 -0
  100. package/docs/rules/server-vs-client.md +105 -0
  101. package/docs/rules/sidebar-order.md +109 -0
  102. package/docs/rules/single-react.md +97 -0
  103. package/docs/rules/size-renderer-bundle.md +109 -0
  104. package/docs/rules/size-section-css.md +95 -0
  105. package/docs/rules/slot-group-marker.md +127 -0
  106. package/docs/rules/slot-marker-literal.md +148 -0
  107. package/docs/rules/typecheck.md +103 -0
  108. package/docs/rules/unlayered-fence.md +113 -0
  109. package/docs/theme-and-css.md +193 -82
  110. package/package.json +4 -9
  111. package/tsconfig.json +1 -1
@@ -1,119 +1,231 @@
1
1
  ---
2
- purpose: The kit's two CSS entries, the Tailwind import rule, the TokenTheme a template supplies, and the breakpoint ladder.
2
+ purpose: The kit's two CSS entries, how a section's own CSS reaches the page, the TokenTheme a template supplies, and the breakpoint ladder.
3
3
  status: living
4
- related: [primitives.md, schema-system.md, eslint.md]
5
- updated: 2026-07-14
4
+ related: [primitives.md, schema-system.md, eslint.md, rules/INDEX.md, check.md]
5
+ updated: 2026-07-15
6
6
  ---
7
7
  # Theme and CSS
8
8
 
9
- The kit owns the **contract**: the Tailwind utility namespace (`text-ink`,
10
- `bg-surface-alt`, `rounded-md`), the breakpoint ladder, the reset, and the section box
11
- model. Your template owns the **values** behind that namespace, declared once as a
12
- `TokenTheme`.
9
+ Your template owns its design system end to end — **the token names included**. You
10
+ declare the tokens your design actually has in a `TokenTheme`, and `template-kit theme`
11
+ compiles that one declaration into the Tailwind utilities that expose them. Declare a
12
+ colour named `lagoon` and `bg-lagoon` exists.
13
+
14
+ The kit owns only what is true of **every** template: the breakpoint ladder, the spacing
15
+ base unit, the motion defaults, the reset gaps Tailwind's preflight leaves, and the
16
+ section box model. It ships no colour, font, type, radius or shadow of its own, and there
17
+ is no shared file for you to edit to make a token of yours usable.
13
18
 
14
19
  Two CSS entries ship, and they are not interchangeable.
15
20
 
16
21
  | Entry | What it is | How it loads |
17
22
  |---|---|---|
18
- | `@homepages/template-kit/styles.css` | The Tailwind v4 `@theme inline` alias layer + the breakpoint ladder + `@source` registration of the kit's compiled JS | imported into **your Tailwind entry**, after Tailwind |
19
- | `@homepages/template-kit/base.css` | Cascade-layer order, the reset, document defaults, `.tr-section`, and the universal spacing / motion tokens | its **own stylesheet** on the page — not part of the Tailwind entry |
23
+ | `@homepages/template-kit/styles.css` | The global Tailwind preamble: breakpoint ladder, `--spacing`, motion defaults, and `@source` registration of the kit's compiled JS | imported into **your Tailwind entry**, after Tailwind |
24
+ | `@homepages/template-kit/base.css` | Cascade-layer order, the three reset gaps Tailwind's preflight leaves, `.tr-section`, `.tr-image-frame`, and the motion tokens | its **own stylesheet** on the page — not part of the Tailwind entry |
20
25
 
21
- ## Import Tailwind exactly once
26
+ ## Your Tailwind entry
22
27
 
23
28
  ```css
24
29
  /* your CSS entry */
25
30
  @import "tailwindcss";
26
31
  @import "@homepages/template-kit/styles.css";
32
+ @import "./theme.css"; /* generated by `template-kit theme` from theme.ts */
27
33
  ```
28
34
 
29
- `styles.css` deliberately does **not** `@import "tailwindcss"` itself. Exactly one
30
- `@import "tailwindcss"` may exist per build graph — yours. Two double-emit Tailwind's
31
- preflight reset (`box-sizing` declared twice, several KB of duplicate base layer), and
32
- nothing errors to tell you.
35
+ Three constraints, all of them silent when broken:
33
36
 
34
- `styles.css` also carries `@source "./**/*.js"`, which registers the kit's own shipped
35
- `dist` for Tailwind's class scan. That is how the utility classes baked into the kit's
36
- primitives (`Image`'s `bg-surface-alt`, `object-cover`, …) reach your compiled CSS; it
37
- also overrides Tailwind's default exclusion of `node_modules`. You do not need to add a
38
- `@source` for the kit yourself.
37
+ - **Import Tailwind exactly once per build graph.** `styles.css` deliberately does not
38
+ `@import "tailwindcss"` itself yours does. Two double-emit Tailwind's preflight reset
39
+ (`box-sizing` declared twice, several KB of duplicate base layer), and nothing errors.
40
+ - **Your generated theme must be imported here, inside the Tailwind build graph.** It
41
+ contains an `@theme` block, which is a Tailwind directive: anywhere outside the graph it
42
+ is inert, and you get a stylesheet with no token utilities and no error explaining why.
43
+ - **Import it after `styles.css`**, so the kit's globals are in place before your tokens
44
+ land on top of them.
39
45
 
40
- ## The page's stylesheets
46
+ `styles.css` also carries an `@source` line registering the kit's own shipped `dist` for
47
+ Tailwind's class scan. That is how the utility classes baked into the kit's primitives
48
+ (`Image`'s `object-cover`, …) reach your compiled CSS; it also overrides Tailwind's default
49
+ exclusion of `node_modules`. You do not need to add a `@source` for the kit yourself.
41
50
 
42
- A published page loads, in order:
51
+ ## `TokenTheme` your template's design system
43
52
 
44
- 1. **`base.css`** layer order, reset, `.tr-section`, universal tokens.
45
- 2. **Your theme's `:root` block** the `--tr-*` values, produced by `compileThemeToCss`.
46
- 3. **Your compiled Tailwind bundle** `@import "tailwindcss"` + `styles.css` + your
47
- section markup's utilities.
53
+ A template's `theme.ts` exports a `TokenTheme`: six open records of tokens, plus the
54
+ document defaults that dress the page. `theme.ts` is the file you write; run
55
+ `template-kit theme` to compile it into the `theme.css` your Tailwind entry imports
56
+ (see [Generating `theme.css`](#generating-themecss) below).
48
57
 
49
- `base.css` reads `--tr-color-ink`, `--tr-color-background`, `--tr-font-sans`,
50
- `--tr-text-base` and `--tr-header-height` for the document defaults, so the theme block
51
- must be present on the page too. The layer order it declares is:
58
+ ```ts
59
+ import type { TokenTheme } from "@homepages/template-kit";
52
60
 
53
- ```css
54
- @layer theme, base, components, sections, template, utilities;
61
+ export const theme: TokenTheme = {
62
+ colors: { ink: "#111418", page: "#ffffff", "surface-alt": "#f4f4f5", lagoon: "#1c6e7d" },
63
+ fonts: { sans: '"Inter", system-ui, sans-serif', display: '"Canela", Georgia, serif' },
64
+ type: { base: "1rem", "2xl": "1.5rem", display: "clamp(2.5rem, 6vw, 4.5rem)" },
65
+ radii: { md: "6px", pill: "9999px" },
66
+ shadows: { card: "0 1px 2px rgb(0 0 0 / 0.06)" },
67
+ layout: { "container-max": "1280px", "header-height": "72px" },
68
+
69
+ // Optional font-loading block, emitted ahead of everything else.
70
+ fontFaces: '@import url("https://fonts.example/inter.css");',
71
+
72
+ // Which of YOUR tokens dress the document.
73
+ document: { font: "sans", color: "ink", background: "page", fontSize: "base" },
74
+ };
55
75
  ```
56
76
 
57
- Utilities come last, so a Tailwind utility always beats the reset's element defaults.
77
+ Nothing is required to exist. Tailwind's own defaults survive underneath your tokens
78
+ `text-2xl`, `rounded-lg`, `red-500` all still work — so a sparse theme is a working theme.
79
+ (The breakpoint ladder is the one exception: `sm:`, `xl:` and `2xl:` are deliberately
80
+ switched off. See below.)
58
81
 
59
- ## `TokenTheme` your template's values
82
+ ### Namespaces and the utilities they produce
60
83
 
61
- A template's `theme.ts` exports a `TokenTheme`. `compileThemeToCss` turns it into the
62
- `:root { --tr-*: }` block the template's CSS bundle ships deterministic (stable key
63
- order, palette keys sorted), so the same theme always content-hashes identically.
84
+ Each record maps to one Tailwind theme namespace. A token's key is emitted **verbatim**
85
+ into both the custom property and the utility no camelCase-to-kebab transform, so what
86
+ you type is what you get.
64
87
 
65
- ```ts
66
- import { compileThemeToCss, type TokenTheme } from "@homepages/template-kit";
88
+ | Record | Custom property | Tailwind utilities | Example |
89
+ |---|---|---|---|
90
+ | `colors` | `--tr-color-<name>` | `bg-`, `text-`, `border-`, `ring-`, `fill-`, `stroke-`, … | `lagoon` → `bg-lagoon` |
91
+ | `fonts` | `--tr-font-<name>` | `font-<name>` | `display` → `font-display` |
92
+ | `type` | `--tr-text-<name>` | `text-<name>` | `2xl` → `text-2xl` |
93
+ | `radii` | `--tr-radius-<name>` | `rounded-<name>` | `pill` → `rounded-pill` |
94
+ | `shadows` | `--tr-shadow-<name>` | `shadow-<name>` | `card` → `shadow-card` |
95
+ | `layout` | `--tr-<name>` | *(none)* | `header-height` → `var(--tr-header-height)` |
67
96
 
68
- export const theme: TokenTheme = {
69
- colors: { ink: "#111418", background: "#ffffff", /* …every role, see below */ },
70
- palette: { "brand-gold": "#c8a248" }, // optional escape hatch
71
- fonts: {
72
- body: '"Inter", system-ui, sans-serif',
73
- heading: '"Canela", Georgia, serif',
74
- numeric: '"Inter", system-ui, sans-serif',
75
- label: '"Inter", system-ui, sans-serif',
76
- fontFaces: '@import url("https://fonts.example/inter.css");', // optional, emitted before :root
77
- },
78
- type: { xs: "0.75rem", sm: "0.875rem", base: "1rem", /* lg xl 2xl 3xl 4xl 5xl display */ },
79
- radii: { sm: "2px", md: "6px", lg: "12px", full: "9999px" },
80
- shadows: { sm: "…", md: "…", lg: "…" },
81
- layout: { containerMax: "1280px", containerPad: "1.5rem", headerHeight: "72px" },
82
- };
97
+ `layout` has no Tailwind utility namespace, so its tokens get a `--tr-*` value and no
98
+ utility reference them with `var()` from your own CSS.
99
+
100
+ ### Token names
101
+
102
+ Kebab-case: `[a-z0-9]` groups separated by single hyphens (`ink`, `ink-soft`, `bp2`).
103
+ `TokenNameSchema` is exported if you want to validate names yourself.
104
+
105
+ **One token, one utility.** Two utility names cannot share a token — if you want both
106
+ `bg-brand` and `bg-primary`, declare both.
107
+
108
+ ### The `document` block
109
+
110
+ Required. It names which of your **own** tokens dress the page — the `body` font-family,
111
+ colour, background, and font-size. `font` and `fontSize` resolve against `fonts` and
112
+ `type`; `color` and `background` resolve against `colors`.
113
+
114
+ Every reference is validated against your declared tokens, in its own group: a typo, or a
115
+ colour name where a font belongs, fails at parse rather than rendering an unthemed page in
116
+ silence.
83
117
 
84
- const css = compileThemeToCss(theme);
118
+ ### Validation
119
+
120
+ `TokenThemeSchema` validates the object at runtime, and it is `.strict()` — an unknown
121
+ top-level key is an error rather than a silently-dead set of tokens.
122
+
123
+ ## Generating `theme.css`
124
+
125
+ `template-kit theme` loads `templates/<key>/theme.ts`, validates its `theme` export
126
+ against `TokenThemeSchema`, and writes `templates/<key>/theme.css` — the file your
127
+ Tailwind entry imports.
128
+
129
+ ```bash
130
+ template-kit theme # infer the template from cwd, or the workspace's one template
131
+ template-kit theme <template> # generate one named template's theme.css, by its key
132
+ template-kit theme --all # generate every template in the workspace
85
133
  ```
86
134
 
87
- `TokenThemeSchema` validates it at runtime; the object is `.strict()`, so an unknown key
88
- is an error rather than a silently-dead token. Every scale is **complete or invalid** —
89
- there are no defaults to fall back on.
135
+ `theme.css` is generated — it opens with a header saying so, and it is not meant to be
136
+ hand-edited. Re-run `template-kit theme` after changing `theme.ts` and commit the
137
+ regenerated file alongside it.
138
+
139
+ A missing `theme.ts`, a `theme.ts` that doesn't export `theme`, or a `theme` that fails
140
+ `TokenThemeSchema` all fail the command with a message naming the file and the problem —
141
+ nothing is written in that case.
142
+
143
+ In order, the file it writes contains:
144
+
145
+ 1. your `fontFaces` block, if you set one;
146
+ 2. `@theme inline { --color-<name>: var(--tr-color-<name>); … }` — the alias layer, which
147
+ is what brings your token utilities into existence;
148
+ 3. `:root { --tr-color-<name>: <value>; … }` — the values behind those aliases;
149
+ 4. `body { … }` — your `document` defaults, written as `var()` indirections so a runtime
150
+ `:root` override still cascades to the page.
151
+
152
+ Deterministic: keys are sorted, so the same theme always content-hashes identically.
153
+
154
+ ## Colour in sections
155
+
156
+ A section carries no palette of its own, in server-rendered code or inside an island — the
157
+ `template-kit/no-hex` [lint rule](rules/no-hex.md) flags a hard-coded hex literal in a
158
+ colour context, and a CSS colour function anywhere, without exempting `"use client"` files
159
+ the way most of the preset's other rules do. Sections spend the theme's tokens; the theme
160
+ is where a colour is named.
161
+
162
+ For a colour the theme does not name — a hover, active, or pressed step, or a translucent
163
+ tint — derive it from the tokens that *are* named, with a `color-mix()` over theme-derived
164
+ arguments:
165
+ `hover:bg-[color-mix(in_srgb,var(--tr-color-primary)_80%,var(--tr-color-surface))]`. Such a
166
+ mix composes a colour out of the theme rather than stating one, and is the single colour
167
+ function `no-hex` allows — it carves out no other.
168
+
169
+ ## How a section's CSS reaches the page
170
+
171
+ A section's compiled CSS layer is assembled from two sources, both automatic — you
172
+ never write an import to make either happen:
173
+
174
+ 1. **Hand-written CSS, collected by path.** Every `.css` file inside the section's
175
+ folder — `styles.css` and any other stylesheet nested under it — is picked up by
176
+ walking the folder, wherever it sits, and concatenated into `@layer sections`. A
177
+ template's own hand-written stylesheet is collected the same way, into
178
+ `@layer template`.
179
+ 2. **Package CSS, harvested from the render path.** The build also bundles
180
+ `Renderer.tsx`'s module graph — and any component that carries `export const
181
+ enhancer` — and pulls in whatever `.css` those imports resolve to. A bare specifier
182
+ (`import "swiper/css";`) resolves into `node_modules` and lands in the same
183
+ `@layer sections`; that is the sanctioned way for a third-party package's
184
+ stylesheet to reach the page.
185
+
186
+ Two constraints follow directly, and both are enforced by lint:
187
+
188
+ - **Don't re-import your own stylesheet.** It is already collected by path —
189
+ importing it again from a `.ts`/`.tsx` file only double-emits it, and can smuggle a
190
+ stray `@layer` statement into a nested layer.
191
+ See [`no-css-import-from-render-path`](rules/no-css-import-from-render-path.md).
192
+ - **`@import` never works inside hand-written CSS, package or local.** An `@import`
193
+ landing inside `@layer sections` / `@layer template` is invalid CSS, and the
194
+ browser drops it silently rather than erroring — the package's styling is simply
195
+ missing from the published page. Reach a package's stylesheet from code instead.
196
+ See [`no-bare-css-import`](rules/no-bare-css-import.md).
90
197
 
91
- ### Color roles
198
+ ## The page's stylesheets
92
199
 
93
- The role keys are fixed. Each maps 1:1 to a `--tr-color-*` variable, and the Tailwind
94
- utility is named after **the variable**, not the role key — so `surfaceAlt` is
95
- `bg-surface-alt`, and `primaryStrong` is `bg-primary-darker`. Read the mapping off the
96
- exported `COLOR_ROLE_VARS` (and `FONT_ROLE_VARS`, `TYPE_STEP_VARS`, `RADIUS_VARS`,
97
- `SHADOW_VARS`, `LAYOUT_VARS` for the other scales), or let your editor autocomplete the
98
- `ColorRole` union:
200
+ A published page loads, in order:
99
201
 
100
- | Role key | CSS variable | Utility |
101
- |---|---|---|
102
- | `ink` `inkSoft` `inkMute` | `--tr-color-ink[-soft|-mute]` | `text-ink`, `text-ink-soft`, `text-ink-mute` |
103
- | `background` `surface` `surfaceAlt` | `--tr-color-background` / `-surface` / `-surface-alt` | `bg-surface-alt`, … |
104
- | `border` `borderStrong` | `--tr-color-border[-strong]` | `border-border-strong`, … |
105
- | `primary` `primaryFg` `primaryStrong` | `--tr-color-primary` / `-primary-fg` / `-primary-darker` | `bg-primary`, `text-primary-fg`, `bg-primary-darker` |
106
- | `accent` `accentFg` `accentStrong` | `--tr-color-accent` / `-accent-contrast` / `-accent-strong` | `bg-accent`, `text-accent-contrast`, `bg-accent-strong` |
107
- | `overlay` `overlayStrong` `overlayLight` | `--tr-color-overlay[-strong|-light]` | `bg-overlay`, … |
108
- | `darkBg` `darkFg` `darkFgMute` `darkBorder` | `--tr-color-footer-bg` / `-footer-fg` / `-footer-fg-mute` / `-footer-divider` | `bg-footer-bg`, `text-footer-fg`, `border-footer-divider`, … |
109
-
110
- `palette` is the open escape hatch: each key becomes `--tr-color-<key>`. Sections normally
111
- use the semantic roles — reach for a brand swatch only when no role honestly fits.
112
-
113
- A section carries no palette of its own, in server-rendered code or inside an island —
114
- the `template-kit/no-hex` [lint rule](eslint.md#universal--no-island-exemption) bans a
115
- hard-coded hex literal or CSS color function in a color context and does not exempt
116
- `"use client"` files, unlike most of the preset's other rules.
202
+ 1. **`base.css`** layer order, the reset gaps, `.tr-section`, `.tr-image-frame`, motion
203
+ tokens.
204
+ 2. **Your compiled Tailwind bundle** your entry: `@import "tailwindcss"` + the kit's
205
+ `styles.css` + your compiled theme + the utilities your section markup uses.
206
+
207
+ The layer order `base.css` declares is:
208
+
209
+ ```css
210
+ @layer theme, base, components, sections, template, utilities;
211
+ ```
212
+
213
+ Utilities come last, so a Tailwind utility always beats the reset's element defaults.
214
+
215
+ ### base.css requires a Tailwind entry
216
+
217
+ `base.css` does **not** ship a reset. Your entry's `@import "tailwindcss"` brings
218
+ Tailwind's preflight, and preflight is the reset box-sizing, margins, list-style, the
219
+ button and anchor resets. `base.css` carries only the three declarations preflight does
220
+ not make: `font-smoothing`, `button { cursor: pointer }` (Tailwind v4 leaves buttons at
221
+ `cursor: default`), and `svg { max-width: 100% }` (preflight caps only `img` and
222
+ `video`).
223
+
224
+ Ship `base.css` without a Tailwind entry on the page and you get an unreset page.
225
+
226
+ `base.css` references **no design token of yours** — nothing global is opinionated about
227
+ your brand. Its one tintable surface is the `Image` frame's placeholder fill; see
228
+ [`Image`](primitives.md#image--the-defensive-image-primitive).
117
229
 
118
230
  ## The breakpoint ladder
119
231
 
@@ -145,8 +257,7 @@ memory — enough to make it evict a live WebGL map's layer on scroll, blanking
145
257
  they stop consuming that memory.
146
258
 
147
259
  The kit does **not** apply this for you — it has a real cost (below), and only you know
148
- whether your page needs it. If it does, add the rule to the same CSS entry that does
149
- `@import "tailwindcss"; @import "@homepages/template-kit/styles.css";` — the third
260
+ whether your page needs it. If it does, add the rule to your Tailwind entry the second
150
261
  stylesheet the page loads:
151
262
 
152
263
  ```css
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homepages/template-kit",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Authoring kit for HomePages marketing-section templates: schema system, contract primitives, theme tokens, and the template-kit CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -58,6 +58,7 @@
58
58
  "build": "tsdown",
59
59
  "typecheck": "tsc --noEmit",
60
60
  "lint": "eslint .",
61
+ "pretest": "node test/link-fixture-workspace.mjs",
61
62
  "test": "node --import tsx --test 'src/**/*.test.ts' 'src/**/*.test.tsx'",
62
63
  "lint:pkg": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints styles.css base.css",
63
64
  "verify:consumer": "node scripts/verify-consumer.mjs",
@@ -67,15 +68,9 @@
67
68
  "release": "changeset publish"
68
69
  },
69
70
  "peerDependencies": {
70
- "eslint": "^9.0.0",
71
71
  "react": "^19.0.0",
72
72
  "react-dom": "^19.0.0"
73
73
  },
74
- "peerDependenciesMeta": {
75
- "eslint": {
76
- "optional": true
77
- }
78
- },
79
74
  "devDependencies": {
80
75
  "@arethetypeswrong/cli": "^0.18.5",
81
76
  "@changesets/cli": "^2.29.8",
@@ -85,6 +80,7 @@
85
80
  "@types/node": "^20.17.10",
86
81
  "@types/react": "^19.0.0",
87
82
  "@types/react-dom": "^19.0.0",
83
+ "@typescript-eslint/parser": "^8.60.1",
88
84
  "esbuild": "^0.28.1",
89
85
  "eslint": "^9.39.4",
90
86
  "jsdom": "^26.1.0",
@@ -98,7 +94,6 @@
98
94
  "typescript-eslint": "^8.60.1"
99
95
  },
100
96
  "dependencies": {
101
- "@typescript-eslint/parser": "^8.60.1",
102
- "zod": "^3.23.8"
97
+ "zod": "^4.4.3"
103
98
  }
104
99
  }
package/tsconfig.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "compilerOptions": {
4
4
  "noEmit": true
5
5
  },
6
- "exclude": ["node_modules", "dist", ".tmp-consumer"]
6
+ "exclude": ["node_modules", "dist", ".tmp-consumer", "test/fixtures"]
7
7
  }