@codenhub/styles 0.7.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.es.md +9 -8
- package/README.md +8 -7
- package/README.pt-BR.md +9 -8
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/cyber.css +1 -1
- package/dist/aesthetics/glass.css +1 -1
- package/dist/aesthetics/index.css +2 -2
- package/dist/aesthetics/neobrutalism.css +1 -1
- package/dist/aesthetics/pixel.css +1 -1
- package/dist/aesthetics/sketch.css +1 -1
- package/dist/cli.js +358 -0
- package/dist/components.css +50 -2
- package/dist/index.css +50 -2
- package/dist/index.d.ts +1 -0
- package/dist/native.css +50 -2
- package/dist/palette.css +1 -1
- package/dist/purge/classes.json +1 -0
- package/dist/purge/index.css +50 -0
- package/dist/theme.css +2 -2
- package/docs/accessibility.md +46 -27
- package/docs/changelog/0.8.0.md +70 -0
- package/docs/changelog/0.9.0.md +40 -0
- package/docs/changelog/index.md +2 -0
- package/docs/es/accessibility.md +47 -28
- package/docs/es/index.md +4 -3
- package/docs/es/integrating/index.md +38 -10
- package/docs/es/integrating/tailwind.md +79 -8
- package/docs/es/integrating/without-a-bundler.md +157 -5
- package/docs/es/setup.md +43 -34
- package/docs/es/usage/aesthetics.md +20 -14
- package/docs/es/usage/buttons.md +6 -2
- package/docs/es/usage/composing.md +6 -2
- package/docs/es/usage/content-and-layout.md +10 -4
- package/docs/es/usage/customizing.md +46 -26
- package/docs/es/usage/feedback.md +5 -1
- package/docs/es/usage/forms.md +30 -5
- package/docs/es/usage/index.md +7 -3
- package/docs/es/usage/presets.md +92 -0
- package/docs/es/usage/solo.md +4 -2
- package/docs/es/usage/theming.md +29 -16
- package/docs/es/usage/tooltips.md +2 -2
- package/docs/index.md +3 -2
- package/docs/integrating/index.md +38 -10
- package/docs/integrating/tailwind.md +78 -7
- package/docs/integrating/without-a-bundler.md +156 -4
- package/docs/pt-BR/accessibility.md +47 -28
- package/docs/pt-BR/index.md +4 -3
- package/docs/pt-BR/integrating/index.md +38 -10
- package/docs/pt-BR/integrating/tailwind.md +79 -8
- package/docs/pt-BR/integrating/without-a-bundler.md +157 -5
- package/docs/pt-BR/setup.md +43 -34
- package/docs/pt-BR/usage/aesthetics.md +20 -14
- package/docs/pt-BR/usage/buttons.md +6 -2
- package/docs/pt-BR/usage/composing.md +6 -2
- package/docs/pt-BR/usage/content-and-layout.md +10 -4
- package/docs/pt-BR/usage/customizing.md +46 -26
- package/docs/pt-BR/usage/feedback.md +5 -1
- package/docs/pt-BR/usage/forms.md +30 -5
- package/docs/pt-BR/usage/index.md +7 -3
- package/docs/pt-BR/usage/presets.md +92 -0
- package/docs/pt-BR/usage/solo.md +4 -2
- package/docs/pt-BR/usage/theming.md +22 -9
- package/docs/pt-BR/usage/tooltips.md +2 -2
- package/docs/setup.md +31 -22
- package/docs/usage/aesthetics.md +19 -13
- package/docs/usage/buttons.md +5 -1
- package/docs/usage/composing.md +5 -1
- package/docs/usage/content-and-layout.md +9 -3
- package/docs/usage/customizing.md +45 -25
- package/docs/usage/feedback.md +4 -0
- package/docs/usage/forms.md +29 -4
- package/docs/usage/index.md +6 -2
- package/docs/usage/presets.md +91 -0
- package/docs/usage/solo.md +3 -1
- package/docs/usage/theming.md +21 -8
- package/docs/usage/tooltips.md +1 -1
- package/llms-full.txt +695 -134
- package/llms.txt +3 -2
- package/package.json +117 -65
- package/{dist/tw → tw}/aesthetics/chunky-tile.css +43 -20
- package/{dist/tw → tw}/aesthetics/cyber.css +19 -25
- package/{dist/tw → tw}/aesthetics/glass.css +110 -36
- package/{dist/tw → tw}/aesthetics/neobrutalism.css +41 -21
- package/{dist/tw → tw}/aesthetics/pixel.css +3 -0
- package/tw/aesthetics/sketch.css +435 -0
- package/{dist/tw → tw}/box.css +109 -26
- package/tw/components/button.css +11 -0
- package/tw/components/button.rules.css +187 -0
- package/tw/components/feedback.css +11 -0
- package/{dist/tw/components/feedback.css → tw/components/feedback.rules.css} +156 -33
- package/tw/components/form.css +11 -0
- package/{dist/tw/components/form.css → tw/components/form.rules.css} +584 -141
- package/tw/components/index.css +16 -0
- package/tw/components/rules.css +41 -0
- package/tw/components/surface.css +11 -0
- package/tw/components/surface.rules.css +225 -0
- package/tw/components/tooltip.css +11 -0
- package/{dist/tw/components/tooltip.css → tw/components/tooltip.rules.css} +82 -61
- package/{dist/tw → tw}/corner.css +18 -12
- package/{dist/tw → tw}/elevation.css +27 -21
- package/{dist/tw → tw}/index.css +11 -9
- package/tw/index.d.ts +1 -0
- package/tw/intent.css +312 -0
- package/tw/modifiers.css +127 -0
- package/tw/native.css +323 -0
- package/tw/presentation.css +139 -0
- package/tw/reset.css +10 -0
- package/{dist/tw/reset.css → tw/reset.rules.css} +7 -8
- package/tw/size.css +34 -0
- package/tw/surface.css +9 -0
- package/tw/surface.rules.css +212 -0
- package/{dist/tw → tw}/theme.css +93 -29
- package/tw/typography.css +8 -0
- package/{dist/tw/typography.css → tw/typography.rules.css} +0 -3
- package/{dist/tw → tw}/utilities/content.css +248 -108
- package/tw/utilities/index.css +11 -0
- package/tw/utilities/layout.css +210 -0
- package/tw/utilities/rules.css +8 -0
- package/dist/tw/aesthetics/sketch.css +0 -398
- package/dist/tw/components/button.css +0 -139
- package/dist/tw/components/index.css +0 -35
- package/dist/tw/components/surface.css +0 -184
- package/dist/tw/intent.css +0 -229
- package/dist/tw/native.css +0 -203
- package/dist/tw/palette.css +0 -460
- package/dist/tw/presentation.css +0 -68
- package/dist/tw/surface.css +0 -61
- package/dist/tw/utilities/index.css +0 -6
- package/dist/tw/utilities/layout.css +0 -144
- /package/{dist/tw → tw}/aesthetics/index.css +0 -0
package/llms-full.txt
CHANGED
|
@@ -45,12 +45,13 @@ The docs build one account settings screen from start to finish, in this order:
|
|
|
45
45
|
|
|
46
46
|
## Requirements
|
|
47
47
|
|
|
48
|
-
| Requirement | Details
|
|
49
|
-
| ------------- |
|
|
50
|
-
| CSS imports | Consumer tooling must resolve package CSS imports.
|
|
51
|
-
| Browsers | Chrome 123, Safari 17.5, or Firefox 128 and newer (`light-dark()` theming; `:has()` for `.input-group` state; `@property` for non-inheriting elevation).
|
|
52
|
-
| Tailwind CSS | 4.3.2 or newer, below 5,
|
|
53
|
-
|
|
|
48
|
+
| Requirement | Details |
|
|
49
|
+
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| CSS imports | Consumer tooling must resolve package CSS imports. |
|
|
51
|
+
| Browsers | Chrome 123, Safari 17.5, or Firefox 128 and newer (`light-dark()` theming; `:has()` for `.input-group` state; `@property` for non-inheriting elevation). |
|
|
52
|
+
| Tailwind CSS | 4.3.2 or newer, below 5, only for the `/tw` source entrypoints. Install it yourself; the package does not. |
|
|
53
|
+
| Node.js | 24 or newer on the machine that runs the `chs build` command. The stylesheets need none, but the package declares it as its engine, so Yarn 1, and npm with `engine-strict`, refuse the install on an older Node.js. |
|
|
54
|
+
| Accessibility | Consumers provide semantic HTML, ARIA, keyboard behavior, and focus management. |
|
|
54
55
|
|
|
55
56
|
## Notes
|
|
56
57
|
|
|
@@ -58,7 +59,7 @@ This package has no JavaScript behavior. It ships no compatibility aliases: a re
|
|
|
58
59
|
|
|
59
60
|
## License
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
This project is licensed under the [Apache-2.0](LICENSE) license. The embedded checkbox marks are derived from the [Lucide](https://lucide.dev) `check` and `minus` icons (ISC), which Lucide derives from [Feather](https://github.com/feathericons/feather) (MIT). See [NOTICE](NOTICE) for the required notices.
|
|
62
63
|
|
|
63
64
|
<!-- Source: docs/index.md -->
|
|
64
65
|
|
|
@@ -119,7 +120,7 @@ The package ships the same classes two ways:
|
|
|
119
120
|
Know these costs before you adopt it:
|
|
120
121
|
|
|
121
122
|
- **Class names are short and unprefixed.** `.primary`, `.success`, `.dark`, `.label`, `.field`, `.section`, `.stack`, `.flat`, and `.ghost` are global names. Another stylesheet on the same page that uses them collides with this one. The package sits in [cascade layers](docs/setup.md#cascade-layers), so your own unlayered CSS wins a collision, but a third-party library's rules for the same name still apply alongside the package's.
|
|
122
|
-
- **The complete stylesheets are large before compression.** At the time of writing, minified, the complete stylesheet is about
|
|
123
|
+
- **The complete stylesheets are large before compression.** At the time of writing, minified, the complete stylesheet is about 198 KB, `/native` about 295 KB, and `/components` about 159 KB. Served with gzip they are about 17 KB, 22 KB, and 13 KB. Focused files are much smaller: `/theme` is about 12 KB (3 KB with gzip) and one aesthetic is 4 to 14 KB (1 to 2 KB with gzip). Most of the size is composition that every component repeats, which is why compression removes so much of it. A compiled file ships every class, whether a page uses it or not. To ship only the classes your pages use, compile the package in your own build: through [Tailwind CSS v4](docs/integrating/tailwind.md) when the project runs it, or with the package's [`build` command](docs/integrating/without-a-bundler.md#build-only-the-classes-you-use) in any other project. A page with one button gets 26 to 28 KB minified that way, and about 6 KB with gzip. Otherwise import the narrowest entrypoint that covers a page; see [Setup → All entrypoints](docs/setup.md#all-entrypoints).
|
|
123
124
|
- **`--color-foreground` is a background, not a text color.** It is the tone one step off the page that a quiet card or panel sits on. The text color is `--color-text`.
|
|
124
125
|
- **Four hover colors are fixed values.** `--color-success-hover`, `--color-warning-hover`, `--color-destructive-hover`, and `--color-info-hover` are precomputed, because mixing them live crashed WebKit. If you override `--color-success` alone, its hover keeps the old hue, so replace the whole family; see [Theming → Color tokens](docs/usage/theming.md#color-tokens).
|
|
125
126
|
- **Modern browsers only.** Chrome 123, Safari 17.5, or Firefox 128 and newer. There is no fallback layer for older browsers.
|
|
@@ -137,7 +138,7 @@ The package is below 1.0 and follows the [semantic versioning rule for 0.x relea
|
|
|
137
138
|
- A minor release, such as 0.7.0 to 0.8.0, may break. It can rename or remove a class or token, or change a default. Every break is listed under **Changed** or **Removed** on that version's [changelog](docs/changelog/index.md) page, with what to use instead.
|
|
138
139
|
- A patch release, such as 0.7.0 to 0.7.1, never breaks.
|
|
139
140
|
|
|
140
|
-
npm's default range for a new dependency, `^0.
|
|
141
|
+
npm's default range for a new dependency, `^0.9.0`, already accepts patches only, because below 1.0 a caret range does not cross into the next minor version. Read the changelog before raising it. When you link a file from a CDN, put the exact version in the URL; see [Without a bundler](docs/integrating/without-a-bundler.md).
|
|
141
142
|
|
|
142
143
|
Breaks are not softened with aliases because the model is still settling, and keeping each old name beside its replacement would grow the surface every release. A rename or removal usually costs one find-and-replace when you upgrade; a changed default may need your own CSS retuned.
|
|
143
144
|
|
|
@@ -157,6 +158,7 @@ Read these in order the first time. Each page adds to the settings screen and en
|
|
|
157
158
|
- [Aesthetics](docs/usage/aesthetics.md): restyle the whole screen with one class.
|
|
158
159
|
- [Solo classes](docs/usage/solo.md): paint an aesthetic onto a component this package does not style.
|
|
159
160
|
- [Composing](docs/usage/composing.md), [Customizing](docs/usage/customizing.md), and [Content and layout](docs/usage/content-and-layout.md): lookup pages with every axis rule, token, default, and helper class.
|
|
161
|
+
- [Presets](docs/usage/presets.md): set a look once, for a region, a theme, or your own component class, instead of repeating it on every element.
|
|
160
162
|
5. [Accessibility](docs/accessibility.md): what the CSS handles for you and what your application must still do.
|
|
161
163
|
|
|
162
164
|
The [changelog](docs/changelog/index.md) lists every release and what changed in it.
|
|
@@ -202,9 +204,11 @@ The complete stylesheet includes the reset, the tokens, the layout and type help
|
|
|
202
204
|
|
|
203
205
|
Pick one:
|
|
204
206
|
|
|
205
|
-
- **Compiled CSS** (`@codenhub/styles`, shown above). Use it unless you have a reason not to. Any bundler that resolves CSS imports from packages works, such as Vite, webpack, or the Tailwind CLI.
|
|
207
|
+
- **Compiled CSS** (`@codenhub/styles`, shown above). Use it unless you have a reason not to. Any bundler that resolves CSS imports from packages works, such as Vite, webpack, or the Tailwind CLI. It ships every class, whether your pages use it or not.
|
|
206
208
|
- **Tailwind CSS v4 source** (`@codenhub/styles/tw`). Use it when your project already runs Tailwind v4. Your build compiles the package with your own utilities, so you get one stylesheet and Tailwind's `dark:` variant matches the package's theme. It needs Tailwind 4.3.2 or newer, below 5.
|
|
207
|
-
- **No bundler.** Link the compiled file from `node_modules` or a CDN with a `<link>` tag.
|
|
209
|
+
- **No bundler.** Link the compiled file from `node_modules` or a CDN with a `<link>` tag; see [Without a bundler](docs/integrating/without-a-bundler.md).
|
|
210
|
+
|
|
211
|
+
To ship only the classes your pages use in a project that does not run Tailwind, with a bundler or without one, the package's `chs build` command reads your files and writes a stylesheet with only those classes, which you import or link in place of the compiled one; see [Build only the classes you use](docs/integrating/without-a-bundler.md#build-only-the-classes-you-use).
|
|
208
212
|
|
|
209
213
|
Your framework may need its own wiring, such as where the global stylesheet goes in Next.js or Astro. Each of these guides covers one setup and then sends you back here:
|
|
210
214
|
|
|
@@ -229,16 +233,18 @@ The order of the two imports does not matter, because every entrypoint declares
|
|
|
229
233
|
## Requirements
|
|
230
234
|
|
|
231
235
|
- **Browsers:** Chrome 123, Safari 17.5, or Firefox 128 and newer. Colors use `light-dark()` and `color-scheme` (Firefox 120). `.input-group` reads a nested control's invalid and disabled state with `:has()` (Firefox 121). The elevation classes register a non-inheriting property with `@property` (Firefox 128); without it, an elevation class would also reach the unclassed elements inside it.
|
|
232
|
-
- **Tooling:** an `@import` of a package name needs a tool that resolves CSS imports from packages. Without one, link the compiled file by its path or from a CDN; see [Without a bundler](docs/integrating/without-a-bundler.md).
|
|
233
|
-
- **Tailwind:** 4.3.2 or newer, below 5, only for the `/tw` source entrypoints. It is an optional peer dependency, so the compiled entrypoints do not install it.
|
|
236
|
+
- **Tooling:** an `@import` of a package name needs a tool that resolves CSS imports from packages through their `exports` map. Without one, link the compiled file by its path or from a CDN; see [Without a bundler](docs/integrating/without-a-bundler.md). Two common tools need a nudge, described in [Integrating → Bundlers that need a nudge](docs/integrating/index.md#bundlers-that-need-a-nudge): Parcel, and PostCSS with `postcss-import`.
|
|
237
|
+
- **Tailwind:** 4.3.2 or newer, below 5, only for the `/tw` source entrypoints. It is an optional peer dependency, so the compiled entrypoints and the `chs build` command do not install it.
|
|
238
|
+
- **Node.js:** 24 or newer on the machine that runs the `chs build` command. The stylesheets need none, but the package declares it as its engine, so Yarn 1, and npm with `engine-strict`, refuse the install on an older Node.js.
|
|
234
239
|
- **Your application:** the package has no JavaScript. Semantic HTML, ARIA, keyboard behavior, focus management, validation, and announcements are yours; see [Accessibility](docs/accessibility.md).
|
|
235
240
|
|
|
236
241
|
## When nothing is styled
|
|
237
242
|
|
|
238
243
|
- **The import is missing or not loaded globally.** The classes do nothing until the stylesheet is on the page. Import it once in a stylesheet every page loads, not in a component that may not render.
|
|
239
|
-
- **The package name does not resolve.** A plain `<link>` or a tool without CSS package resolution cannot read `@import "@codenhub/styles"`. Link `node_modules/@codenhub/styles/dist/index.css`, or follow [Without a bundler](docs/integrating/without-a-bundler.md).
|
|
244
|
+
- **The package name does not resolve.** A plain `<link>` or a tool without CSS package resolution cannot read `@import "@codenhub/styles"`. Link `node_modules/@codenhub/styles/dist/index.css`, or follow [Without a bundler](docs/integrating/without-a-bundler.md). Parcel and `postcss-import` resolve it with a small change; see [Integrating → Bundlers that need a nudge](docs/integrating/index.md#bundlers-that-need-a-nudge).
|
|
240
245
|
- **A `/tw` path is loaded without Tailwind.** The `/tw` files are Tailwind source: they contain `@theme`, `@utility`, and `@apply`, which browsers do not understand. Your Tailwind build has to compile them; see [Tailwind CSS v4](docs/integrating/tailwind.md).
|
|
241
|
-
- **A
|
|
246
|
+
- **A focused `/tw` entrypoint is imported without Tailwind before it.** `@codenhub/styles/tw/button` and the other focused component files do not import Tailwind themselves. Imported alone, they compile without an error but produce no component rules. Put `@import "tailwindcss";` before them; see [Tailwind CSS v4 → Using a focused entrypoint](docs/integrating/tailwind.md#using-a-focused-entrypoint).
|
|
247
|
+
- **A class built at runtime is missing (Tailwind source only).** Tailwind generates a utility class, the package's components such as `.btn` included, only when it finds the name in your source files. A name assembled from strings, such as `` `btn ${tone}` ``, is invisible to that scan. Write full class names, or list them as described in [Tailwind CSS v4](docs/integrating/tailwind.md).
|
|
242
248
|
|
|
243
249
|
### Inlining critical CSS
|
|
244
250
|
|
|
@@ -248,9 +254,9 @@ That does not apply to the `/tw` files. They are Tailwind source, not finished C
|
|
|
248
254
|
|
|
249
255
|
## All entrypoints
|
|
250
256
|
|
|
251
|
-
Compiled entrypoints are finished CSS. `/tw` entrypoints publish the uncompiled source from `
|
|
257
|
+
Compiled entrypoints are finished CSS. `/tw` entrypoints publish the uncompiled source from `tw/`, beside `dist/`, which a Tailwind v4 build must process. A focused `/tw` component entrypoint includes the theme tokens, so its classes need nothing else from the package, but it needs `@import "tailwindcss";` before it; see [Tailwind CSS v4 → Using a focused entrypoint](docs/integrating/tailwind.md#using-a-focused-entrypoint).
|
|
252
258
|
|
|
253
|
-
Each import path resolves the same file under the `style`, `import`, and `default` export conditions. A CSS import is a side effect, so make sure your bundler does not drop it as unused.
|
|
259
|
+
Each import path resolves the same file under the `style`, `import`, and `default` export conditions. A `types` condition comes first and names an empty declaration, so TypeScript accepts a side-effect import such as `import "@codenhub/styles";` with `noUncheckedSideEffectImports`, which TypeScript 6 turns on by default. A CSS import is a side effect, so make sure your bundler does not drop it as unused.
|
|
254
260
|
|
|
255
261
|
| Import path | What it contains and what it changes |
|
|
256
262
|
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -258,7 +264,7 @@ Each import path resolves the same file under the `style`, `import`, and `defaul
|
|
|
258
264
|
| `@codenhub/styles/theme` | Compiled tokens and the intent, presentation, elevation, and corner-pattern classes. Sets root theme properties and the light and dark selectors. No reset and no components. |
|
|
259
265
|
| `@codenhub/styles/palette` | Generated `--palette-*` custom properties: every intent and presentation combination's fill, text, and border color as a flat value, plus flat `border`, `surface`, and `text` colors. For code that cannot use the package itself; see [Customizing → Generated palette](docs/usage/customizing.md#generated-palette). |
|
|
260
266
|
| `@codenhub/styles/components` | Compiled tokens, typography helpers, and every component class. Each component carries its own reduced-motion rule, and the closed-`<dialog>` fix comes with `.card`. No reset, no layout or content helpers, no classless mappings. |
|
|
261
|
-
| `@codenhub/styles/native` |
|
|
267
|
+
| `@codenhub/styles/native` | Compiled tokens, typography, layout and content helpers, components, and the package reset, plus [classless mappings](#classless-html) for plain HTML elements. Not Tailwind's Preflight. Applies the reset and broad element selectors. |
|
|
262
268
|
| `@codenhub/styles/aesthetics` | All six compiled aesthetics, `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch`, each with its `-solo` class. No tokens or components of its own; import it in any order. |
|
|
263
269
|
| `@codenhub/styles/aesthetics/*` | One compiled aesthetic and its solo class: `/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, `/cyber`, or `/sketch`. |
|
|
264
270
|
| `@codenhub/styles/tw` | Source version of the complete stylesheet. Imports Tailwind itself, then the theme, typography, helpers, components, and reset. Same global rules. |
|
|
@@ -270,9 +276,9 @@ Each import path resolves the same file under the `style`, `import`, and `defaul
|
|
|
270
276
|
| `@codenhub/styles/tw/feedback` | Source theme plus `.alert`, `.badge`, `.skeleton`, and `.progress`, with their animations. |
|
|
271
277
|
| `@codenhub/styles/tw/tooltip` | Source theme plus the tooltip classes. |
|
|
272
278
|
| `@codenhub/styles/tw/reset` | Source theme, typography helpers, and the global reset. Changes the root, body, and elements, and sets selection, motion, focus, scrollbar, and forced-colors rules. |
|
|
273
|
-
| `@codenhub/styles/tw/native` | Source version of `/native`: everything plus the reset and the classless mappings. The broadest global effect.
|
|
279
|
+
| `@codenhub/styles/tw/native` | Source version of `/native`: everything but Tailwind's Preflight, plus the package reset and the classless mappings. The broadest global effect. |
|
|
274
280
|
| `@codenhub/styles/tw/typography` | Source theme plus the typography helpers. Sets the theme selectors; no reset, no classless mappings. |
|
|
275
|
-
| `@codenhub/styles/tw/utilities` | Source theme and typography plus the layout and content helpers.
|
|
281
|
+
| `@codenhub/styles/tw/utilities` | Source theme and typography plus the layout and content helpers. |
|
|
276
282
|
| `@codenhub/styles/tw/aesthetics` | All six aesthetics as source. `/tw/aesthetics/neobrutalism`, `/glass`, `/pixel`, `/chunky-tile`, `/cyber`, and `/sketch` publish one each. Plain CSS with no theme or reset side effects. |
|
|
277
283
|
| `@codenhub/styles/package.json` | The package manifest, as JSON. For tools that read the installed version or export map; it is not a stylesheet. |
|
|
278
284
|
|
|
@@ -280,12 +286,12 @@ Do not import two entrypoints that overlap, such as `@codenhub/styles` and `@cod
|
|
|
280
286
|
|
|
281
287
|
## Classless HTML
|
|
282
288
|
|
|
283
|
-
`@codenhub/styles/native` styles plain HTML elements without any classes, for content you cannot add classes to, such as Markdown output or a CMS body. It
|
|
289
|
+
`@codenhub/styles/native` styles plain HTML elements without any classes, for content you cannot add classes to, such as Markdown output or a CMS body. It holds everything the complete stylesheet does except Tailwind's Preflight, the browser-default reset that `@codenhub/styles` brings with Tailwind. The package's own reset is in it: box sizing, the root background, text color and font, the margin reset on headings, paragraphs, and lists, selection colors, focus rings, and the reduced-motion rule. Then it adds these mappings:
|
|
284
290
|
|
|
285
291
|
| Element | Styled as |
|
|
286
292
|
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
287
293
|
| `h1`, `h2`, `h3`, `h4`, `h5` and `h6` | `.text-title-lg`, `.text-title`, `.text-title-sm`, `.text-label-lg`, `.text-label` |
|
|
288
|
-
| `
|
|
294
|
+
| `body` | `.text-body`, inherited by paragraphs, list items, and other text |
|
|
289
295
|
| `table` | `.data-table` |
|
|
290
296
|
| `kbd`, `blockquote`, `q`, `code`, `pre` | `.kbd`, `.quote`, `.quote-inline`, `.code`, `.pre` |
|
|
291
297
|
| `hr` | `.divider` |
|
|
@@ -293,6 +299,8 @@ Do not import two entrypoints that overlap, such as `@codenhub/styles` and `@cod
|
|
|
293
299
|
| `textarea`, `select` | `.textarea`, `.select` |
|
|
294
300
|
| `input type="checkbox"`, `input type="radio"` | `.checkbox`, `.radio` |
|
|
295
301
|
| `button`, `input type="button"`, `type="submit"`, `type="reset"` | `.btn` |
|
|
302
|
+
| `dialog` | `.card.floating`: hidden until opened, then a centered floating card |
|
|
303
|
+
| `progress` | `.progress`, filled to its `value`; with no `value` it is the indeterminate sweep |
|
|
296
304
|
|
|
297
305
|
```html
|
|
298
306
|
<article>
|
|
@@ -310,7 +318,9 @@ What it does not do:
|
|
|
310
318
|
- **Some input types are left alone:** `file`, `range`, `color`, and `hidden` keep the browser's own look.
|
|
311
319
|
- **No switch.** A checkbox with `role="switch"` is still painted as a checkbox; the mapping does not read roles. A page that needs switches is better served by the class-based stylesheet.
|
|
312
320
|
- **Links keep the text around them.** A link inside a heading stays heading-sized; a link is never styled as a button.
|
|
313
|
-
-
|
|
321
|
+
- **Body text is set once.** `p` and `li` take their type from the element they sit in, so a container that sets its own size, weight, or tracking applies it to the Markdown inside it. Controls keep their own tracking: a button, a badge, and a text field print `normal`, whatever the page or the container sets.
|
|
322
|
+
- **Native behavior stays native.** A `<dialog>` opens with `show()` or `showModal()` as usual; the mapping only paints it. A `<progress>` sweep stops under a reduced-motion preference, and in forced colors the bar uses the system colors.
|
|
323
|
+
- **`pre code` is not double-styled.** Code inside a `pre` drops the chip it would draw on its own: background, padding, border, shadow, glow, and clip.
|
|
314
324
|
|
|
315
325
|
The mappings are global: every matching element on the page is styled, including elements inside third-party widgets. They sit in the `base` [cascade layer](#cascade-layers), so any class beats them. Add an intent or presentation class to a mapped element, such as `<button class="destructive">`, and it applies as it would on `.btn`.
|
|
316
326
|
|
|
@@ -318,18 +328,19 @@ The mappings are global: every matching element on the page is styled, including
|
|
|
318
328
|
|
|
319
329
|
Every entrypoint puts its CSS into Tailwind's four cascade layers and declares their order before using one, so the result does not depend on which file loads first:
|
|
320
330
|
|
|
321
|
-
| Layer | Holds
|
|
322
|
-
| ------------ |
|
|
323
|
-
| `theme` | Tailwind's theme, the foundation tokens, the theme selectors, and `/palette`.
|
|
324
|
-
| `base` | The reset and the classless element styles.
|
|
325
|
-
| `components` | The intent
|
|
326
|
-
| `utilities` | Every component (`.btn`, `.card`, `.ipt`, and so on), next to your own Tailwind utilities.
|
|
327
|
-
| _unlayered_ | The forced-colors and reduced-motion overrides, the closed-`<dialog>` fix, and the `-solo` classes.
|
|
331
|
+
| Layer | Holds |
|
|
332
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
333
|
+
| `theme` | Tailwind's theme, the foundation tokens, the theme selectors, and `/palette`. |
|
|
334
|
+
| `base` | The reset and the classless element styles. |
|
|
335
|
+
| `components` | The aesthetic classes and the neutral intent values every component starts from. |
|
|
336
|
+
| `utilities` | Every component (`.btn`, `.card`, `.ipt`, and so on), next to your own Tailwind utilities. The intent, presentation, edge, elevation, corner, and size classes sit in `utilities.families`, a sublayer that loses to every other rule in `utilities`. |
|
|
337
|
+
| _unlayered_ | The forced-colors and reduced-motion overrides, the closed-`<dialog>` fix, and the `-solo` classes. |
|
|
328
338
|
|
|
329
339
|
What that means when you write your own CSS:
|
|
330
340
|
|
|
331
|
-
- **Your utility or unlayered CSS beats an intent, presentation, elevation, or aesthetic class** on the same element. `[--ui-fill:50%]` on a `.solid` button, `font-serif` on a `.pixel` region, or `.my-card { --ui-radius: 0 }` in your own stylesheet all apply.
|
|
341
|
+
- **Your utility or unlayered CSS beats an intent, presentation, elevation, or aesthetic class** on the same element. `[--ui-fill:50%]` on a `.solid` button, `font-serif` on a `.pixel` region, or `.my-card { --ui-radius-surface: 0 }` in your own stylesheet all apply.
|
|
332
342
|
- **A component and your utility share the `utilities` layer.** Specificity decides between them, then source order, as between any two utilities. Your unlayered CSS always beats a component.
|
|
343
|
+
- **Your utility beats a component's inner defaults.** `p-0 text-center` on a table cell, `text-sm` on a `.label`, or `px-8` on a `.btn.sm` applies, because the component writes those defaults so that a class on the element wins.
|
|
333
344
|
- **State beats your intent.** A field with `aria-invalid="true"` stays destructive even with an intent utility on it.
|
|
334
345
|
- **The `-solo` classes are unlayered on purpose**, so they beat a third-party component's layered or zero-specificity styles. A more specific unlayered rule, or an equally specific one that loads later, still beats them; see [Solo classes](docs/usage/solo.md).
|
|
335
346
|
|
|
@@ -354,16 +365,44 @@ Importing from one place matters for two reasons. Some frameworks only _allow_ g
|
|
|
354
365
|
|
|
355
366
|
## Which guide to follow
|
|
356
367
|
|
|
357
|
-
| Stack
|
|
358
|
-
|
|
|
359
|
-
| A Tailwind CSS v4 project on any bundler
|
|
360
|
-
| Next.js / React
|
|
361
|
-
| Vue
|
|
362
|
-
| Svelte / SvelteKit
|
|
363
|
-
| Astro
|
|
364
|
-
| Plain HTML, Rails, Django, or any app with no CSS build step | [Without a bundler](docs/integrating/without-a-bundler.md) |
|
|
368
|
+
| Stack | Guide |
|
|
369
|
+
| ----------------------------------------------------------------------------- | ------------------------------------------- |
|
|
370
|
+
| A Tailwind CSS v4 project on any bundler | [Tailwind CSS v4](docs/integrating/tailwind.md) |
|
|
371
|
+
| Next.js / React | [Next.js](docs/integrating/nextjs.md) |
|
|
372
|
+
| Vue | [Vue](docs/integrating/vue.md) |
|
|
373
|
+
| Svelte / SvelteKit | [Svelte](docs/integrating/svelte.md) |
|
|
374
|
+
| Astro | [Astro](docs/integrating/astro.md) |
|
|
375
|
+
| Plain HTML, Rails, Laravel, Django, or any app with no Node.js CSS build step | [Without a bundler](docs/integrating/without-a-bundler.md) |
|
|
376
|
+
|
|
377
|
+
A project with no framework — a plain Vite, webpack, or Parcel build — imports the compiled stylesheet from the project's existing global CSS entry, as [Setup → Import the stylesheet](docs/setup.md#import-the-stylesheet) shows. Vite and webpack resolve the package name as it is; Parcel and PostCSS with `postcss-import` need the change below. The compiled stylesheet holds every class; to ship only the ones your pages use without adopting Tailwind, write a trimmed stylesheet with the package's [`build` command](docs/integrating/without-a-bundler.md#build-only-the-classes-you-use) and import that instead.
|
|
378
|
+
|
|
379
|
+
### Bundlers that need a nudge
|
|
380
|
+
|
|
381
|
+
Both tools find the stylesheet once they are told how; neither needs a different entrypoint.
|
|
365
382
|
|
|
366
|
-
|
|
383
|
+
**Parcel.** Turn on its support for a package's `exports` map in your `package.json`:
|
|
384
|
+
|
|
385
|
+
```json
|
|
386
|
+
{
|
|
387
|
+
"@parcel/resolver-default": {
|
|
388
|
+
"packageExports": true
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Then import the package with Parcel's `npm:` prefix, which tells it the name is a package and not a file next to the stylesheet:
|
|
394
|
+
|
|
395
|
+
```css
|
|
396
|
+
@import "npm:@codenhub/styles";
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**PostCSS with `postcss-import`.** The plugin does not read a package's `exports` map, so the package name alone does not resolve. Import the compiled file by its path on disk instead, relative to your own stylesheet, the same file a `<link>` would name:
|
|
400
|
+
|
|
401
|
+
```css
|
|
402
|
+
@import "../node_modules/@codenhub/styles/dist/index.css";
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The other compiled entrypoints work the same way, such as `@codenhub/styles/dist/aesthetics/neobrutalism.css`.
|
|
367
406
|
|
|
368
407
|
## Verifying it worked
|
|
369
408
|
|
|
@@ -383,11 +422,11 @@ Once the button renders, go back to the main path: [Building a screen](docs/conc
|
|
|
383
422
|
|
|
384
423
|
# Tailwind CSS v4
|
|
385
424
|
|
|
386
|
-
A project that already runs Tailwind CSS v4 can compile this package's source instead of loading its compiled output. Your app's utilities and the package's classes then come out of one build, share one theme, and use one `dark:` variant. This page covers the import, aesthetics, the focused entrypoints, and the build problems you are likely to meet.
|
|
425
|
+
A project that already runs Tailwind CSS v4 can compile this package's source instead of loading its compiled output. Your app's utilities and the package's classes then come out of one build, share one theme, and use one `dark:` variant. This page covers the import, aesthetics, the focused entrypoints, building your own components with `@apply`, dropping Tailwind's palette, and the build problems you are likely to meet.
|
|
387
426
|
|
|
388
427
|
## Why source instead of compiled
|
|
389
428
|
|
|
390
|
-
`/tw` entrypoints publish copied, uncompiled source from `
|
|
429
|
+
`/tw` entrypoints publish copied, uncompiled source from the package's `tw/` folder; your own Tailwind build compiles it alongside your app, so its `@theme`, `@utility`, and `@apply` directives resolve together with yours. The compiled entrypoints (`@codenhub/styles`) still work in a Tailwind project, but then two separate CSS pipelines run side by side instead of one — use `/tw` unless a specific reason rules it out.
|
|
391
430
|
|
|
392
431
|
## Import the source entrypoint
|
|
393
432
|
|
|
@@ -399,7 +438,7 @@ A project that already runs Tailwind CSS v4 can compile this package's source in
|
|
|
399
438
|
|
|
400
439
|
`/tw` imports Tailwind itself, so it replaces your own `@import "tailwindcss"` rather than joining it: with both, Tailwind's Preflight is emitted twice. Every other `/tw/*` entrypoint expects your import to come first; see [Using a focused entrypoint](#using-a-focused-entrypoint).
|
|
401
440
|
|
|
402
|
-
Tailwind CSS v4 scans your project automatically — most projects need nothing beyond the import above. `@source` paths resolve relative to the CSS file that declares them, not your project root, so `./src` here means "the `src/` next to this stylesheet"; adjust it to match where your own global stylesheet actually lives. Add `@source` only for markup Tailwind's automatic scan won't reach on its own: a path excluded by default (`.gitignore`d, or inside `node_modules` — an external component library, for instance) or, in a monorepo, one outside this file's own directory tree. Point it at your app's markup, not at this package
|
|
441
|
+
Tailwind CSS v4 scans your project automatically — most projects need nothing beyond the import above. `@source` paths resolve relative to the CSS file that declares them, not your project root, so `./src` here means "the `src/` next to this stylesheet"; adjust it to match where your own global stylesheet actually lives. Add `@source` only for markup Tailwind's automatic scan won't reach on its own: a path excluded by default (`.gitignore`d, or inside `node_modules` — an external component library, for instance) or, in a monorepo, one outside this file's own directory tree. Point it at your app's markup, not at this package: the package's source names every class it defines, so scanning it emits all of them whether your pages use them or not.
|
|
403
442
|
|
|
404
443
|
## Add aesthetics
|
|
405
444
|
|
|
@@ -423,6 +462,71 @@ Unlike `/tw`, the focused entrypoints and `/tw/native` do not import Tailwind, s
|
|
|
423
462
|
@import "@codenhub/styles/tw/button";
|
|
424
463
|
```
|
|
425
464
|
|
|
465
|
+
## Build your own components with `@apply`
|
|
466
|
+
|
|
467
|
+
On `/tw`, every component, helper, and axis class is a Tailwind utility. That includes the intents (`neutral`, `primary`, `secondary`, `success`, `warning`, `destructive`, `info`), the presentation (`solid`, `soft`, `ghost`, `edged`, `edgeless`), the elevation (`flat`, `raised`, `floating`), the corner patterns (`cut-diagonal`, `cut-diagonal-reverse`), and the sizes (`sm`, `lg`). So one class of yours can carry a whole recipe:
|
|
468
|
+
|
|
469
|
+
```css
|
|
470
|
+
@import "@codenhub/styles/tw";
|
|
471
|
+
|
|
472
|
+
@utility btn-save {
|
|
473
|
+
@apply btn primary soft sm;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@utility btn-danger {
|
|
477
|
+
@apply btn destructive md:lg dark:solid;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
```html
|
|
482
|
+
<button class="btn-save" type="submit">Save</button>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
- **An applied component behaves like the class.** `btn-save` hovers, presses, takes focus, and follows an aesthetic the way `class="btn primary soft sm"` does.
|
|
486
|
+
- **Variants work.** `hover:primary`, `md:lg`, and `dark:solid` apply an axis class under that condition, in your markup or inside `@apply`.
|
|
487
|
+
- **Write your class as `@utility`.** It then stays in the `utilities` layer, so your other utilities on the same element still beat it. A plain `.btn-save { @apply … }` rule works too, but it is unlayered, so what it applies beats every utility on the element; [Customizing → Composition helpers](docs/usage/customizing.md#composition-helpers) has the details.
|
|
488
|
+
- **Modifiers apply too.** A component's modifiers, such as `.tight` on `.stack`, `.icon` on `.btn`, or `.interactive` on `.card`, are utilities as well, so `@apply stack tight`, `@apply btn icon pill`, and `md:tight` work. The disabled state is the exception: it stays an attribute or the `.disabled` class in markup.
|
|
489
|
+
- **Your utility beats a component's inner defaults.** `p-0 text-center` on a `.data-table` cell, `text-sm` on a `.label`, and `px-8` on a `.btn.sm` all apply.
|
|
490
|
+
|
|
491
|
+
[Presets](docs/usage/presets.md) compares this with the other ways to set a look once.
|
|
492
|
+
|
|
493
|
+
## Drop Tailwind's palette
|
|
494
|
+
|
|
495
|
+
`/tw` builds on Tailwind's default theme, so Tailwind's palette utilities, such as `bg-red-500`, stay available. If your design uses only the package's tokens, clear the hues you do not use:
|
|
496
|
+
|
|
497
|
+
```css
|
|
498
|
+
@import "@codenhub/styles/tw";
|
|
499
|
+
|
|
500
|
+
@theme {
|
|
501
|
+
--color-red-*: initial;
|
|
502
|
+
--color-orange-*: initial;
|
|
503
|
+
--color-yellow-*: initial;
|
|
504
|
+
--color-lime-*: initial;
|
|
505
|
+
--color-green-*: initial;
|
|
506
|
+
--color-teal-*: initial;
|
|
507
|
+
--color-cyan-*: initial;
|
|
508
|
+
--color-sky-*: initial;
|
|
509
|
+
--color-blue-*: initial;
|
|
510
|
+
--color-violet-*: initial;
|
|
511
|
+
--color-purple-*: initial;
|
|
512
|
+
--color-fuchsia-*: initial;
|
|
513
|
+
--color-pink-*: initial;
|
|
514
|
+
--color-slate-*: initial;
|
|
515
|
+
--color-gray-*: initial;
|
|
516
|
+
--color-zinc-*: initial;
|
|
517
|
+
--color-stone-*: initial;
|
|
518
|
+
--color-black: initial;
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
The package's own colors do not change, and you can still add colors of your own in the same block.
|
|
523
|
+
|
|
524
|
+
The block leaves out `neutral`, `emerald`, `amber`, `rose`, `indigo`, and `white` on purpose: the theme builds its colors from them.
|
|
525
|
+
|
|
526
|
+
- **Do not clear the whole namespace.** `--color-*: initial` also removes the package's semantic colors, and the build stops with an error such as `Cannot apply unknown utility class`.
|
|
527
|
+
- **Do not clear a ramp the theme reads.** Clearing `--color-emerald-*` builds without an error, but the success colors made from it are lost.
|
|
528
|
+
- **Do not clear the type, font, or spacing namespaces.** The components apply Tailwind's `text-*` and `font-*` utilities and its `--spacing()` function, so `--text-*: initial`, `--font-*: initial`, `--spacing: initial`, or `--*: initial` stops the build with `Cannot apply unknown utility class` or `The --spacing(…) function requires that the --spacing theme variable exists`. Override single values instead, such as `--text-sm`. Clearing `--radius-*` is safe.
|
|
529
|
+
|
|
426
530
|
## Troubleshooting
|
|
427
531
|
|
|
428
532
|
**Classes resolve to no styling**
|
|
@@ -439,11 +543,17 @@ Unlike `/tw`, the focused entrypoints and `/tw/native` do not import Tailwind, s
|
|
|
439
543
|
|
|
440
544
|
**A class built at runtime has no styling**
|
|
441
545
|
|
|
442
|
-
- **What happened:** `<button class="btn primary">` is styled, but a button whose
|
|
443
|
-
- **Why:** Tailwind
|
|
444
|
-
- **Fix:** write complete class
|
|
546
|
+
- **What happened:** `<button class="btn primary">` is styled, but a button whose class comes from a variable, such as `"btn " + variant`, or from CMS content, is not.
|
|
547
|
+
- **Why:** Tailwind emits only the classes it finds written out in the files it scans. That holds for the package's component classes too: a `/tw` build emits `.alert` only if `alert` appears somewhere it scans, which keeps the stylesheet to what your pages use. The intent, presentation, elevation, corner, and size classes, such as `.primary`, `.soft`, and `.sm`, are utilities too, so the same holds for them. The aesthetic classes, such as `.glass`, are plain CSS and always emitted. A name assembled at runtime never appears whole in a file, so the scan misses it.
|
|
548
|
+
- **Fix:** write each complete class name somewhere Tailwind scans, for example in a lookup object (`{ save: "btn primary", remove: "btn destructive" }`), or list the names your code or content can produce in your stylesheet:
|
|
445
549
|
|
|
446
|
-
|
|
550
|
+
```css
|
|
551
|
+
@import "@codenhub/styles/tw";
|
|
552
|
+
|
|
553
|
+
@source inline("btn alert badge primary destructive soft");
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Every `/tw` entrypoint requires Tailwind CSS 4.3.2 or newer, below 5 (`>=4.3.2 <5`).
|
|
447
557
|
|
|
448
558
|
## The `dark:` variant
|
|
449
559
|
|
|
@@ -623,7 +733,10 @@ The compiled stylesheets are finished CSS: each one is a single file with no `@i
|
|
|
623
733
|
|
|
624
734
|
What needs a build tool is the import _name_. `@import "@codenhub/styles"` names a package, and finding the file behind that name — `node_modules/@codenhub/styles/dist/index.css` — is done by Node-aware tooling such as Vite, webpack, or the Tailwind CLI, which read the package's `exports` map. A browser does not: it resolves an `@import` URL against the stylesheet that contains it and a `<link href>` against the page, so a bare package name becomes a request for a path that does not exist. Without a bundler, link the file by its path instead.
|
|
625
735
|
|
|
626
|
-
The `/tw` entrypoints are Tailwind source and need a Tailwind build. `/tw/aesthetics` is the exception, plain CSS with nothing for Tailwind to process, but without a bundler link the compiled `dist/aesthetics/` files instead, as below. This page
|
|
736
|
+
The `/tw` entrypoints are Tailwind source and need a Tailwind build. `/tw/aesthetics` is the exception, plain CSS with nothing for Tailwind to process, but without a bundler link the compiled `dist/aesthetics/` files instead, as below. This page is for a page that has no Node.js CSS build: plain HTML, Rails, Laravel, Django, or any server-rendered app. There are two paths:
|
|
737
|
+
|
|
738
|
+
1. **Link the compiled stylesheet.** It needs nothing but static files. You ship every class, whether your pages use it or not.
|
|
739
|
+
2. **Build a stylesheet with the package's `chs build` command.** It scans your templates and writes a stylesheet with only the classes they use. See [Build only the classes you use](#build-only-the-classes-you-use).
|
|
627
740
|
|
|
628
741
|
## Which file to link
|
|
629
742
|
|
|
@@ -639,14 +752,17 @@ Each compiled import path is one file in the package's `dist/` folder:
|
|
|
639
752
|
| `@codenhub/styles/aesthetics` | `dist/aesthetics/index.css` |
|
|
640
753
|
| `@codenhub/styles/aesthetics/*` | `dist/aesthetics/<name>.css`, such as `dist/aesthetics/pixel.css` |
|
|
641
754
|
|
|
642
|
-
Link
|
|
755
|
+
Link one base file, not several, since they overlap, and any aesthetics you use. Their order does not matter, because every file declares the [cascade layers](docs/setup.md#cascade-layers) before using one. [Setup → All entrypoints](docs/setup.md#all-entrypoints) explains what each one contains.
|
|
643
756
|
|
|
644
757
|
## From a CDN
|
|
645
758
|
|
|
646
759
|
jsDelivr and unpkg serve every file of every published npm version, so a page can link one with nothing installed:
|
|
647
760
|
|
|
648
761
|
```html
|
|
649
|
-
<
|
|
762
|
+
<head>
|
|
763
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@codenhub/styles@0.9.0/dist/index.css" />
|
|
764
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@codenhub/styles@0.9.0/dist/aesthetics/pixel.css" />
|
|
765
|
+
</head>
|
|
650
766
|
```
|
|
651
767
|
|
|
652
768
|
Put the exact version in the URL. Without one, the CDN serves the latest release, and below 1.0 a minor release may rename or remove a class; see [Versioning](docs/index.md#versioning).
|
|
@@ -692,6 +808,8 @@ Then link it from the layout, by its path below that folder:
|
|
|
692
808
|
|
|
693
809
|
Point the path at `node_modules/@codenhub` rather than at `dist/` itself. Propshaft serves files by their path below each asset folder, so adding `dist/` directly would publish a bare `index.css` that collides with any `index.css` of your own.
|
|
694
810
|
|
|
811
|
+
The files you link are in `dist/`, which holds finished CSS with no `@import` to resolve, plus an empty `index.d.ts` for TypeScript and the `chs build` command with the files it reads. The Tailwind source lives in `tw/`, outside `dist/`, so a static pipeline that serves or fingerprints `dist/` meets no `@import` it cannot resolve.
|
|
812
|
+
|
|
695
813
|
### Django
|
|
696
814
|
|
|
697
815
|
Add the folder to `STATICFILES_DIRS` under a prefix, which keeps its file names apart from your own static files:
|
|
@@ -708,10 +826,154 @@ STATICFILES_DIRS = [
|
|
|
708
826
|
<link rel="stylesheet" href="{% static 'codenhub-styles/index.css' %}" />
|
|
709
827
|
```
|
|
710
828
|
|
|
829
|
+
Point the prefix at `dist`, not at the package folder. `dist/` holds finished CSS with no `@import` to resolve, plus an empty `index.d.ts` for TypeScript and the `chs build` command with the files it reads, which the storage copies as they are, so `collectstatic` also works with `ManifestStaticFilesStorage`, which fingerprints each file and rewrites the references inside it. The package folder also holds the Tailwind source in `tw/`, whose `@import "tailwindcss/..."` lines that storage cannot resolve.
|
|
830
|
+
|
|
711
831
|
### Laravel
|
|
712
832
|
|
|
713
833
|
A Laravel app runs Vite by default, so it has a bundler: import the package from `resources/css/app.css` as in [Setup → Import the stylesheet](docs/setup.md#import-the-stylesheet), and load that file with the `@vite` directive as usual.
|
|
714
834
|
|
|
835
|
+
## Build only the classes you use
|
|
836
|
+
|
|
837
|
+
The package carries a command, `chs`, that reads your templates for class names and writes one minified stylesheet with only the StyleKit classes they use. A page that uses one button gets about an eighth of the full stylesheet; the exact size depends on which classes you use, since each component brings the shared rules it relies on.
|
|
838
|
+
|
|
839
|
+
The command works with a bundler too. A project whose bundler does not run Tailwind, such as a plain Vite or webpack build, can write the stylesheet into its sources, `--output src/stylekit.css`, and import that file where it would import `@codenhub/styles`. A project that runs Tailwind CSS v4 gets the same trimming from the [source entrypoint](docs/integrating/tailwind.md) and does not need the command.
|
|
840
|
+
|
|
841
|
+
It needs Node.js 24 or newer on the machine that builds the CSS, whether that is your laptop, a CI job, or a deploy step, and nothing else: the package is the whole install, with no Tailwind and no configuration. Install it as a development dependency from your project root; npm creates a `package.json` if you have none:
|
|
842
|
+
|
|
843
|
+
```sh
|
|
844
|
+
npm install --save-dev @codenhub/styles
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
The stylesheet the command writes is plain CSS, so the servers that serve your site need neither Node.js nor the package. Add the build to your `package.json` scripts, where the installed command is on the path by its name:
|
|
848
|
+
|
|
849
|
+
```json
|
|
850
|
+
{
|
|
851
|
+
"scripts": {
|
|
852
|
+
"build:css": "chs build --content \"app/views/**/*.erb\" --output public/stylekit.css"
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
Then run it from your project root:
|
|
858
|
+
|
|
859
|
+
```sh
|
|
860
|
+
npm run build:css
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
To run it once without a script, call it through the package name:
|
|
864
|
+
|
|
865
|
+
```sh
|
|
866
|
+
npx @codenhub/styles build --content "app/views/**/*.erb" --output public/stylekit.css
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
Do not type `npx chs`. `chs` is the command's name inside this package, but npm also has an unrelated package called `chs`, and `npx chs` runs that one in any folder where this package is not installed.
|
|
870
|
+
|
|
871
|
+
On success the command prints one line, the absolute path it wrote and the file's size in bytes:
|
|
872
|
+
|
|
873
|
+
```text
|
|
874
|
+
/home/you/shop/public/stylekit.css 24497 bytes
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
Link that file from your layout like any static stylesheet.
|
|
878
|
+
|
|
879
|
+
### Rails views
|
|
880
|
+
|
|
881
|
+
```json
|
|
882
|
+
"build:css": "chs build --content \"app/views/**/*.erb\" --content \"app/helpers/**/*.rb\" --output public/stylekit.css"
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
```erb
|
|
886
|
+
<%# app/views/layouts/application.html.erb %>
|
|
887
|
+
<link rel="stylesheet" href="/stylekit.css" />
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### Laravel Blade views
|
|
891
|
+
|
|
892
|
+
```json
|
|
893
|
+
"build:css": "chs build --content \"resources/views/**/*.blade.php\" --output public/css/stylekit.css"
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
```blade
|
|
897
|
+
{{-- resources/views/layouts/app.blade.php --}}
|
|
898
|
+
<link rel="stylesheet" href="{{ asset('css/stylekit.css') }}" />
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Django templates
|
|
902
|
+
|
|
903
|
+
A Django project keeps templates in a project-level `templates/` folder, in each app's own `templates/` folder, or both. Give one `--content` for each place yours live, and leave out any place you do not use, since a glob that matches nothing fails the build:
|
|
904
|
+
|
|
905
|
+
```json
|
|
906
|
+
"build:css": "chs build --content \"templates/**/*.html\" --content \"shop/templates/**/*.html\" --output static/stylekit.css"
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
```django
|
|
910
|
+
{% load static %}
|
|
911
|
+
<link rel="stylesheet" href="{% static 'stylekit.css' %}" />
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
Write the output into a folder listed in `STATICFILES_DIRS`, here `static/`, so `collectstatic` picks it up. The settings above list only the package's folder, so add `BASE_DIR / "static"` to that list if your project does not have it yet.
|
|
915
|
+
|
|
916
|
+
### Options
|
|
917
|
+
|
|
918
|
+
- `--content <glob>`, or `-c <glob>`, names the files to read for class names. It is required and repeatable: pass it once per glob, and the build reads every file any of them matches. A relative glob is resolved against the folder you run the command in, so run it from your project root; an absolute glob is used as given. `**` matches any number of folders, and `{rb,erb}` either extension. Any text file works, whatever its extension. Each glob has to match at least one file, or the build fails and names it, so a typo cannot ship an empty stylesheet. Files your `.gitignore` excludes are still read when a glob matches them, and nothing outside your globs is read.
|
|
919
|
+
- Quote every glob, with `\"` inside a `package.json` script. Without quotes, your shell may expand the glob into a list of files before the command sees it, and only the first file counts as the `--content` value. When that leaves extra file names behind, the build rejects them rather than silently reading less than you meant; when the glob matched a single file, the build reads just that file and nothing your templates add later.
|
|
920
|
+
- `--output <file>`, or `-o <file>`, is the stylesheet to write. It is required. A relative path is resolved against the folder you run the command in, missing parent folders are created, and an existing file is replaced.
|
|
921
|
+
- `--aesthetic <name>` adds one of the [aesthetics](docs/usage/aesthetics.md): `neobrutalism`, `glass`, `pixel`, `chunky-tile`, `cyber`, or `sketch`. Repeat it for more than one; naming one twice includes it once. Aesthetics are opt-in because the build cannot tell from your templates which one you want: each is added when you ask for it, less its rules for classes your templates do not use, and none is included when you do not.
|
|
922
|
+
- `--no-minify` writes readable, indented CSS instead. Useful for reading what the build produced; serve the minified file.
|
|
923
|
+
- `--help` prints the usage, with the aesthetics this version ships. `--version` prints the package version. Both build nothing and exit with status 0.
|
|
924
|
+
|
|
925
|
+
The output always holds the theme, its design tokens as CSS custom properties, the reset, the base rules every component relies on, and the classes a page gets at run time rather than from a template, such as `.theme-dark`. On top of that comes each StyleKit class your templates use. The output is built from the complete stylesheet, not from `/native`, so it has no classless mappings. A build whose templates name every class writes the complete stylesheet, byte for byte.
|
|
926
|
+
|
|
927
|
+
### How classes are found
|
|
928
|
+
|
|
929
|
+
The build does not run your templates. It reads each file your `--content` globs match as plain text, collects every word that could be a class name, and writes the rules for the words that are StyleKit classes. This is how Tailwind CSS reads content too.
|
|
930
|
+
|
|
931
|
+
A class has to appear in a file as one whole word. `class="btn primary"`, `class: "btn primary"`, and `classes = ["btn", "primary"]` all work, and so does a class written before a colon or after one, as in Svelte's `class:primary={isPrimary}` or a Ruby hash's `btn: true`. A word that only looks like a class also counts: a comment that says "card" adds the card's full rules to the stylesheet, which makes the file larger and changes nothing on the page.
|
|
932
|
+
|
|
933
|
+
A class name your code assembles while rendering never appears whole in any file, so the build cannot find it. In `class="btn <%= @order.priority %>"`, `btn` is found, but the intent is whatever `priority` holds at render time; if that is `destructive`, the button renders without its red. Two ways out:
|
|
934
|
+
|
|
935
|
+
- **Write class names whole.** Map each value to a complete class name in a file the build reads, such as `{ "urgent" => "destructive", "normal" => "neutral" }.fetch(@order.priority)` in a helper.
|
|
936
|
+
- **List them in a safelist file.** A plain text file with one class name per line, added with its own `--content`, such as `--content config/stylekit-classes.txt`. Keep it next to the code that builds the class names, so whoever changes one sees the other.
|
|
937
|
+
|
|
938
|
+
### Variants
|
|
939
|
+
|
|
940
|
+
An intent, presentation, or modifier class can take one of ten prefixes, written as in Tailwind: `sm:`, `md:`, `lg:`, `xl:`, and `2xl:` apply it from that breakpoint up, `hover:`, `focus:`, `focus-visible:`, and `active:` while the element is in that state, and `dark:` wherever the dark theme applies, as [Theming → The dark: variant](docs/usage/theming.md#the-dark-variant) describes. `md:lg` makes a button large from the `md` breakpoint, and `hover:primary` gives it the primary intent on hover:
|
|
941
|
+
|
|
942
|
+
```html
|
|
943
|
+
<button class="btn sm md:lg ghost hover:soft">Save</button>
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
That covers intents such as `primary` and `destructive`, the fill and edge classes such as `soft` and `edged`, and modifiers such as `sm`, `lg`, `raised`, `pill`, `tight`, and `ruled`. `disabled` takes none: it is a state the element is in, so write it as the attribute or the class. Component classes such as `btn` and `card`, and the text and layout helpers, take no prefix.
|
|
947
|
+
|
|
948
|
+
Each prefix is a fixed copy of those classes, built into the package, so the list is the common ones rather than all of Tailwind's. The command does not stack prefixes, as in `md:hover:primary`, and offers no other ones, such as `print:` or `group-hover:`. Such a class is not styled, and the element renders without it.
|
|
949
|
+
|
|
950
|
+
### What it does not do
|
|
951
|
+
|
|
952
|
+
- **No Tailwind utilities.** `flex`, `mt-4`, `text-red-500`, and the rest of Tailwind are not StyleKit classes, so the build leaves them out. For layout, StyleKit's own [layout helpers](docs/usage/content-and-layout.md) are in the build like any other class. A project that wants Tailwind's utilities, its other variants, or prefixes stacked runs Tailwind itself and imports the [source entrypoint](docs/integrating/tailwind.md), which gives all three.
|
|
953
|
+
- **No watch mode.** It builds once and exits. Run it again after you change a template, from a script, or from a file watcher your framework or editor already has. A class the stylesheet does not have yet leaves its element unstyled until the next build.
|
|
954
|
+
- **No configuration file and no custom CSS.** You cannot add your own `@theme` values, `@utility` definitions, or plugins. To change the look, override the custom properties in a stylesheet of your own loaded after this one, as [Customizing](docs/usage/customizing.md) describes.
|
|
955
|
+
- **No other commands.** `build` is the only one; `--help` and `--version` are the only flags that run without it.
|
|
956
|
+
|
|
957
|
+
### In a deploy
|
|
958
|
+
|
|
959
|
+
Build the stylesheet before whatever step collects or fingerprints your assets, such as `rails assets:precompile`, `php artisan optimize`, or `python manage.py collectstatic`. Either commit the output, so the servers need no Node.js at all at the cost of a generated file someone has to remember to rebuild, or build it on deploy, so the build machine needs Node.js 24 or newer.
|
|
960
|
+
|
|
961
|
+
Pin the version. Below 1.0 a minor release may rename or remove a class, so the same command can write a different stylesheet with a newer version. The output depends on nothing but the package's version and your templates, so commit `package.json` and the lockfile npm writes, and install with `npm ci` on the build machine, so every build uses the version you tested.
|
|
962
|
+
|
|
963
|
+
### When the build fails
|
|
964
|
+
|
|
965
|
+
Every failure prints one line starting with `chs:` to standard error and exits with status 1, without writing the output file, so a deploy script stops instead of shipping a stale or empty stylesheet. The line says what went wrong and what to do:
|
|
966
|
+
|
|
967
|
+
- **`missing --content` or `missing --output`.** Add the one it names. An `--output` that is empty or only spaces counts as missing.
|
|
968
|
+
- **`--content "<glob>" matches no file in <folder>`.** Usually a typo in the path, a command run from another folder than your project root, or a glob for a place your project does not use. Building from nothing would write a stylesheet with no component classes, and the site would quietly lose its styling.
|
|
969
|
+
- **`unexpected argument "<file>"`.** Almost always a `--content` glob without quotes, which your shell expanded into a list of files. Quote the glob.
|
|
970
|
+
- **`unknown option "<flag>"`.** A typo, or an option from another tool, such as `--watch` or `--config`. `--help` lists the options.
|
|
971
|
+
- **`option "<flag>" is missing its value or does not take one`.** A flag that takes a value was the last thing on the line, or `--no-minify` was given one, as in `--no-minify=true`.
|
|
972
|
+
- **`unknown aesthetic "<name>"; choose from ...`.** Pick one of the names listed. They are lowercase and hyphenated, as in `chunky-tile`.
|
|
973
|
+
- **`missing command` or `unknown command "<name>"`.** The first word after `chs` has to be `build`.
|
|
974
|
+
- **`this command needs Node.js 24 or newer`.** Update Node.js on the machine that builds the CSS.
|
|
975
|
+
- **`cannot write the stylesheet: <reason>`.** `<reason>` is Node.js's own message. `EISDIR` means `--output` names an existing folder, so name a file inside it; `EACCES` or `EPERM` means you cannot write there.
|
|
976
|
+
|
|
715
977
|
## Verifying it worked
|
|
716
978
|
|
|
717
979
|
Render one styled element, as in [Integrating → Verifying it worked](docs/integrating/index.md#verifying-it-worked):
|
|
@@ -938,7 +1200,7 @@ The screen has one primary action and one quiet one. [Buttons](docs/usage/button
|
|
|
938
1200
|
|
|
939
1201
|
# Usage
|
|
940
1202
|
|
|
941
|
-
These guides finish the settings screen started in [Building a screen](docs/concepts.md). Read the first seven in order: each one adds a part of the screen and assumes the ones before it. The
|
|
1203
|
+
These guides finish the settings screen started in [Building a screen](docs/concepts.md). Read the first seven in order: each one adds a part of the screen and assumes the ones before it. The next three are lookup pages; you will be sent to them when a guide needs the full table, and you can come back whenever you need a default or a rule. The last page, Presets, is for when the screen works and you are tired of typing the same classes.
|
|
942
1204
|
|
|
943
1205
|
Guides, in order:
|
|
944
1206
|
|
|
@@ -953,9 +1215,13 @@ Guides, in order:
|
|
|
953
1215
|
Lookup pages:
|
|
954
1216
|
|
|
955
1217
|
- [Composing](docs/usage/composing.md): the complete rules for combining intent, presentation, and elevation, every component's defaults, and which axes each component reads.
|
|
956
|
-
- [Customizing](docs/usage/customizing.md): presentation and material tokens, aesthetic knobs, the
|
|
1218
|
+
- [Customizing](docs/usage/customizing.md): presentation and material tokens, aesthetic knobs, the public component variables, the generated palette, and the composition helpers for building your own components.
|
|
957
1219
|
- [Content and layout](docs/usage/content-and-layout.md): every layout helper, the type scale, cards and panels, tables, quotes, code, and keyboard keys.
|
|
958
1220
|
|
|
1221
|
+
Once the screen works:
|
|
1222
|
+
|
|
1223
|
+
- [Presets](docs/usage/presets.md): set a look once, with axis classes on a container, theme and material tokens, or your own component classes built with `@apply`.
|
|
1224
|
+
|
|
959
1225
|
When you finish, [Accessibility](docs/accessibility.md) collects what the CSS does for you and what your application still owes its users.
|
|
960
1226
|
|
|
961
1227
|
<!-- Source: docs/usage/buttons.md -->
|
|
@@ -995,7 +1261,11 @@ A button accepts one intent, one fill class, and one edge class, all optional. W
|
|
|
995
1261
|
|
|
996
1262
|
A **neutral** button never fills completely. Its color is the page's own text color, so a full fill would be a black or white block, the loudest thing on the page for the intent that means nothing in particular. A neutral `.solid` button stops at a 20% fill instead, a quiet gray a step darker than `.soft`. Every named intent fills to 100%.
|
|
997
1263
|
|
|
998
|
-
On hover, a button's fill deepens by `--hover-step` (14%), so a `.ghost` button picks up a tint and a `.solid` one changes to its intent's hover color. On press it scales down slightly (`scale(0.97)`); an [aesthetic](docs/usage/aesthetics.md) can replace that with its own press, and a user's reduced-motion preference
|
|
1264
|
+
On hover, a button's fill deepens by `--hover-step` (14%), so a `.ghost` button picks up a tint and a `.solid` one changes to its intent's hover color. On press it scales down slightly (`scale(0.97)`); an [aesthetic](docs/usage/aesthetics.md) can replace that with its own press, and under a user's reduced-motion preference it still happens, at once.
|
|
1265
|
+
|
|
1266
|
+
A neutral `.soft` or `.ghost` button inside a filled surface, such as a Cancel button in a `.card.solid.primary` or an `.alert.solid.destructive`, prints its label and its line in that surface's text color, so it stays readable on the fill. A text field does the same. A button with an intent keeps its own color, and so does a neutral `.solid` one, which rests on a fill of its own.
|
|
1267
|
+
|
|
1268
|
+
When a filled button has keyboard focus, a 2px line of the page color runs inside the focus ring, so the ring stays visible on a button filled with the same color, such as `.solid.primary`.
|
|
999
1269
|
|
|
1000
1270
|
## Sizes and shapes
|
|
1001
1271
|
|
|
@@ -1110,6 +1380,25 @@ A field is a label, a control, and an optional hint, stacked:
|
|
|
1110
1380
|
|
|
1111
1381
|
A text control is at least 2.5rem tall (`--control-height`), the same height as a default button, so a field and a button line up in a row. Its default look is no fill and a border. A `.textarea` is at least 7rem tall and can be resized vertically.
|
|
1112
1382
|
|
|
1383
|
+
### Sizes
|
|
1384
|
+
|
|
1385
|
+
`.ipt`, `.select`, `.textarea`, and `.input-group` take the button's size classes, so a small field and a small button line up in a row:
|
|
1386
|
+
|
|
1387
|
+
| Class | Height | Side padding | Text |
|
|
1388
|
+
| ------- | ------ | ------------ | --------- |
|
|
1389
|
+
| `.sm` | 2rem | 0.625rem | 0.875rem |
|
|
1390
|
+
| default | 2.5rem | 0.75rem | inherited |
|
|
1391
|
+
| `.lg` | 3rem | 1rem | 1.0625rem |
|
|
1392
|
+
|
|
1393
|
+
The corners scale with the size, as a button's do. A `.textarea` keeps its 7rem minimum height at every size; the class changes its text, padding, and corners. Size an `.input-group` on the group, not on the control inside it: the control and its adornments take the group's text size.
|
|
1394
|
+
|
|
1395
|
+
```html
|
|
1396
|
+
<div class="cluster tight">
|
|
1397
|
+
<input class="ipt sm" aria-label="Coupon code" />
|
|
1398
|
+
<button class="btn sm" type="button">Apply</button>
|
|
1399
|
+
</div>
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1113
1402
|
## Show a validation error
|
|
1114
1403
|
|
|
1115
1404
|
Mark the control invalid with `aria-invalid="true"` and give the error message `.hint.destructive`:
|
|
@@ -1122,7 +1411,13 @@ Mark the control invalid with `aria-invalid="true"` and give the error message `
|
|
|
1122
1411
|
</div>
|
|
1123
1412
|
```
|
|
1124
1413
|
|
|
1125
|
-
An invalid control draws its border, focus ring, and typed text in the destructive color, whatever intent class it has. The text uses the readable tone of that color, `--intent-strong`, so it stays legible on the page. The package only paints the state: deciding that the value is invalid, setting and clearing `aria-invalid`, and writing the message are your code. Remove `aria-invalid` once the value is valid again; leaving it set keeps the field red.
|
|
1414
|
+
An invalid control draws its border, focus ring, and typed text in the destructive color, whatever intent class it has. The text uses the readable tone of that color, `--intent-strong`, so it stays legible on the page. The package only paints the state: deciding that the value is invalid, setting and clearing `aria-invalid`, and writing the message are your code. Remove `aria-invalid` once the value is valid again; leaving it set keeps the field red. On a checkbox, radio, or switch, `aria-invalid="true"` draws the toggle exactly as `.destructive` does, checked mark included.
|
|
1415
|
+
|
|
1416
|
+
A hint takes any of the six built-in intents, not only `.destructive`: `.hint.success` confirms a value was accepted, and `.hint.warning` flags one that is allowed but unusual. Each prints in its intent's readable tone, `--intent-strong`, which holds 4.5:1 against the page in both themes. A hint with no intent, with `.neutral`, or with an [intent you defined yourself](docs/usage/theming.md#your-own-intent), stays secondary-colored. Only a class on the hint itself colors it; an intent on the field or a container around it does not.
|
|
1417
|
+
|
|
1418
|
+
```html
|
|
1419
|
+
<input class="ipt success" id="username" value="ada" aria-describedby="username-hint" /> <span class="hint success" id="username-hint">That username is available.</span>
|
|
1420
|
+
```
|
|
1126
1421
|
|
|
1127
1422
|
## Put the profile form together
|
|
1128
1423
|
|
|
@@ -1166,7 +1461,7 @@ A `.select` draws its own arrow. With `multiple` or a `size` above 1 it becomes
|
|
|
1166
1461
|
|
|
1167
1462
|
## Add an icon or a fixed prefix
|
|
1168
1463
|
|
|
1169
|
-
`.input-group` puts an icon or a piece of text inside a field's border. The group draws the border, background, focus ring, and invalid and disabled states; the control inside it gives up its own, so the border is drawn once. Place the adornment before the control to put it at the start, after it to put it at the end.
|
|
1464
|
+
`.input-group` puts an icon or a piece of text inside a field's border. The group draws the border, background, focus ring, and invalid and disabled states; the control inside it gives up its own, so the border is drawn once. Place the adornment before the control to put it at the start, after it to put it at the end. A group is the height of a lone field, so it lines up with the fields and buttons beside it.
|
|
1170
1465
|
|
|
1171
1466
|
The profile address always starts with the same host, so it is a fixed prefix:
|
|
1172
1467
|
|
|
@@ -1180,7 +1475,7 @@ The profile address always starts with the same host, so it is a fixed prefix:
|
|
|
1180
1475
|
</div>
|
|
1181
1476
|
```
|
|
1182
1477
|
|
|
1183
|
-
The gap between the control and its adornment is `--input-group-gap`, 0.5rem by default. That suits an icon, but text meant to read as part of the value looks broken with a space after it, so the example sets it to `0`. Set it on the
|
|
1478
|
+
The gap between the control and its adornment is `--input-group-gap`, 0.5rem by default. That suits an icon, but text meant to read as part of the value looks broken with a space after it, so the example sets it to `0`. Set it on the group, or once on a form for every group in it. It is one of the [public component variables](docs/usage/customizing.md#component-variables).
|
|
1184
1479
|
|
|
1185
1480
|
A members search, with an icon at the start (the `ic-lucide-*` classes need `@codenhub/icons` loaded; see [Buttons → Loading icons](docs/usage/buttons.md#loading-icons)):
|
|
1186
1481
|
|
|
@@ -1198,7 +1493,7 @@ A `.select` works in a group too. The group removes the control's side padding,
|
|
|
1198
1493
|
|
|
1199
1494
|
Any element works as the adornment: an icon class, an inline `<svg>`, an `<img>`, or text. The package ships no icons for fields, because a built-in image could not follow your text color, theme, or aesthetic.
|
|
1200
1495
|
|
|
1201
|
-
The group reads intent and presentation the way a lone `.ipt` does: `.input-group.primary` colors the border, and `.input-group.soft.edgeless` is a field sunk into the page. `aria-invalid="true"` or `disabled` on the control inside applies to the whole group, and so does either on the group itself. The focus ring appears when
|
|
1496
|
+
The group reads intent and presentation the way a lone `.ipt` does: `.input-group.primary` colors the border and the typed text, and `.input-group.soft.edgeless` is a field sunk into the page. Put the intent on the group, not on the control inside: the group draws the field, so the control's text takes the group's intent and an intent class on the control has no effect. `aria-invalid="true"` or `disabled` on the control inside applies to the whole group, and so does either on the group itself. The group's focus ring appears when its field has focus, so unlike a lone field it also shows after a mouse click. A button inside the group, such as a clear button, draws its own ring when it has focus, and the group draws none: one ring always marks the one control that has focus.
|
|
1202
1497
|
|
|
1203
1498
|
## Browser decorations
|
|
1204
1499
|
|
|
@@ -1389,6 +1684,8 @@ A badge is a small pill for a status or a count:
|
|
|
1389
1684
|
|
|
1390
1685
|
A badge is soft with no border by default, and takes the same intent and presentation classes as a button: `.badge.warning` for "Payment due", `.badge.info.solid` for a filled "New". Unlike an alert, a badge with no intent keeps its light tint, because it has no border, and without the tint it would disappear into the text around it.
|
|
1391
1686
|
|
|
1687
|
+
A neutral badge that is not `.solid`, such as a count inside a `.btn.primary`, prints in the text color of the filled component it sits in, so it stays readable on that fill. A badge with an intent keeps its own color.
|
|
1688
|
+
|
|
1392
1689
|
## Show a placeholder while loading
|
|
1393
1690
|
|
|
1394
1691
|
While the profile card waits for data, show gray blocks where the text will be:
|
|
@@ -1443,6 +1740,8 @@ When you do not know how far along the work is, such as while the export is bein
|
|
|
1443
1740
|
|
|
1444
1741
|
`.active` adds a shimmer over the track of a determinate bar, to show that work is still happening between updates. Both animations stop when the user asks for reduced motion; an indeterminate bar then rests with its fill at the start.
|
|
1445
1742
|
|
|
1743
|
+
With the [classless stylesheet](docs/setup.md#classless-html), a plain `<progress>` element is drawn as `.progress`: filled to its `value`, or the indeterminate sweep when it has no `value`. It announces its value itself, so it needs no `aria-valuenow`.
|
|
1744
|
+
|
|
1446
1745
|
### How progress bars look
|
|
1447
1746
|
|
|
1448
1747
|
The fill is the intent color at full strength, or `currentColor` with no intent. The track is soft and edgeless by default: a light tint with no border.
|
|
@@ -1534,7 +1833,7 @@ WCAG 1.4.13 asks three things of content that appears on hover or focus. The pac
|
|
|
1534
1833
|
- **Persistent.** It stays open until the pointer and focus leave.
|
|
1535
1834
|
- **Dismissible.** The user must be able to close it without moving the pointer or focus, usually with Escape. CSS cannot listen for a key, so this part is yours.
|
|
1536
1835
|
|
|
1537
|
-
To change the linger time, set `--tooltip-linger` on the `.tooltip` element, for example `style="--tooltip-linger: 500ms"`.
|
|
1836
|
+
To change the linger time, set `--tooltip-linger` on the `.tooltip` element or on any element around it, for example `style="--tooltip-linger: 500ms"`. Keep it long enough for the pointer to reach the bubble. `--tooltip-gap` sets the space between the trigger and the bubble the same way. Both are [public component variables](docs/usage/customizing.md#component-variables).
|
|
1538
1837
|
|
|
1539
1838
|
For Escape, write `data-state="closed"` on `.tooltip`: it hides the bubble even while it is hovered or focused. Remove the attribute only when both the pointer and focus have left the tooltip, so the next hover or focus opens it again. Removing it earlier, while the trigger still has focus, makes the message reappear without the user doing anything.
|
|
1540
1839
|
|
|
@@ -1611,17 +1910,26 @@ A settings screen with a "System / Light / Dark" choice removes the attribute fo
|
|
|
1611
1910
|
|
|
1612
1911
|
How it resolves:
|
|
1613
1912
|
|
|
1614
|
-
- A theme selector sets the CSS `color-scheme` property and
|
|
1615
|
-
- `color-scheme` inherits, so a selector themes everything inside it. A selector on any element themes that part of the page: a dark sidebar on a light page is `<aside class="dark"
|
|
1913
|
+
- A theme selector sets the CSS `color-scheme` property, and the element's text color to `--color-text`. Every color token holds its light and its dark value at once, through `light-dark()`, and the element's `color-scheme` picks one.
|
|
1914
|
+
- `color-scheme` inherits, so a selector themes everything inside it. A selector on any element themes that part of the page: a dark sidebar on a light page is `<aside class="dark">`, and the text inside it takes the dark theme's text color.
|
|
1915
|
+
- A selector sets no background. Give the region its ground yourself, such as `background: var(--color-background)`. Any utility or component class on the same element overrides the selector's text color.
|
|
1616
1916
|
- The nearest selector wins, at any depth. A `.light` card inside a `.dark` page is light.
|
|
1617
1917
|
- With no selector, the root has `color-scheme: light dark`, which follows the system.
|
|
1618
1918
|
- Put one theme on an element. If an element carries both a light and a dark selector, dark wins, but do not rely on that.
|
|
1619
|
-
- Setting `color-scheme` yourself on an element
|
|
1919
|
+
- Setting `color-scheme` yourself on an element themes the tokens the same way, because it is the only thing the palette reads. It also themes native parts such as scrollbars and the inside of form controls. Plain text inside keeps the color it inherited until you also set `color: var(--color-text)`.
|
|
1620
1920
|
|
|
1621
1921
|
Every component color comes from a token or from `currentColor`, so components follow the theme with no dark-mode rules of their own. The marks the package draws, such as the checkbox tick and the select arrow, use `currentColor` too. Icons and images you add are yours to theme.
|
|
1622
1922
|
|
|
1623
1923
|
`light-dark()` works from Chrome 123, Safari 17.5, and Firefox 120. The package as a whole needs Firefox 128; see [Setup → Requirements](docs/setup.md#requirements).
|
|
1624
1924
|
|
|
1925
|
+
### Printing
|
|
1926
|
+
|
|
1927
|
+
A page prints in the light theme, whatever theme it shows on screen. Under `@media print`, the root and every dark selector set `color-scheme: light`, so every token resolves to its light value, a forced-dark sidebar's included. Paper is light, and dark text on it costs less ink than a printed dark page.
|
|
1928
|
+
|
|
1929
|
+
Filled components print their background. A browser drops backgrounds from a printed page by default, which leaves a solid button's contrast text on nothing, so every component sets `print-color-adjust: exact`. The property inherits, so the content inside a component keeps its colors too.
|
|
1930
|
+
|
|
1931
|
+
One limit: a `dark:` utility still applies in print. It matches by selector, not by `color-scheme`, so `<html class="dark">` with `dark:bg-black` on an element prints that background black. Write a `print:` utility beside it if the printed page matters.
|
|
1932
|
+
|
|
1625
1933
|
### The `dark:` variant
|
|
1626
1934
|
|
|
1627
1935
|
With the Tailwind source entrypoints (`/tw`), the package replaces Tailwind's `dark:` variant so your own utilities follow the same theme as the tokens. A `dark:` utility applies under an explicit dark selector on the element or an ancestor, or under a dark system preference when no ancestor sets a theme:
|
|
@@ -1639,7 +1947,7 @@ With the Tailwind source entrypoints (`/tw`), the package replaces Tailwind's `d
|
|
|
1639
1947
|
|
|
1640
1948
|
It differs from the tokens in one case: an explicit theme nested inside the opposite one. A `.light` element inside a `.dark` one has light tokens, because the nearest `color-scheme` wins. But `dark:` matches by selector, and `.dark *` matches every element inside `.dark`, so a `dark:` utility inside that `.light` element still applies. If a page uses both tokens and `dark:` utilities, do not nest opposite themes.
|
|
1641
1949
|
|
|
1642
|
-
The compiled entrypoints do not define a `dark:` variant; they have no Tailwind build to define it in.
|
|
1950
|
+
The compiled entrypoints do not define a `dark:` variant; they have no Tailwind build to define it in. The [`chs build` command](docs/integrating/without-a-bundler.md#variants) does offer `dark:`, with the same selectors, on the intent, presentation, and modifier classes only.
|
|
1643
1951
|
|
|
1644
1952
|
## Change the brand color
|
|
1645
1953
|
|
|
@@ -1673,6 +1981,8 @@ The same pattern works for every family in [Color tokens](#color-tokens): `--col
|
|
|
1673
1981
|
|
|
1674
1982
|
The values must be complete colors: hex, `rgb()`, `oklch()`, or a variable you declared yourself. Tailwind palette variables such as `--color-teal-700` exist in a `/tw` build, from your Tailwind theme, but the compiled CSS declares only the palette colors the package itself uses. With compiled CSS, a reference to any other palette variable is empty, and the color it feeds is lost.
|
|
1675
1983
|
|
|
1984
|
+
With `/tw`, you can drop the Tailwind hues your design does not use, so `bg-red-500` and its siblings stop being classes. Keep the ones the theme reads, `neutral`, `emerald`, `amber`, `rose`, `indigo`, and `white`: cleared, the colors built from them are lost without an error. [Tailwind CSS v4 → Drop Tailwind's palette](docs/integrating/tailwind.md#drop-tailwinds-palette) has the block to copy.
|
|
1985
|
+
|
|
1676
1986
|
## Your own intent
|
|
1677
1987
|
|
|
1678
1988
|
The package ships six intents: `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, and `.info`, and the default, `.neutral`. To add one, such as a "Beta" color for features in preview, write a class that sets the seven intent slots. Use complete colors, with `light-dark()` for anything that changes between themes:
|
|
@@ -1702,6 +2012,8 @@ The class works on everything that takes an intent: buttons, badges, fields and
|
|
|
1702
2012
|
- **A quiet, untinted rest.** A neutral `.card.soft`, `.panel`, and `.alert` rest on the quiet background tone, and they recognize an intent only by the six built-in classes. `.card.soft.beta`, `.panel.beta`, and `.alert.beta` are drawn like the neutral one: the quiet background tone instead of a violet tint. Your intent still colors their text and border.
|
|
1703
2013
|
- **The chip tint.** A `.beta` key cap or code block shows your `--intent-subtle` tint, but not the extra 12% of `--intent-color` a built-in intent adds over it.
|
|
1704
2014
|
- **Solo classes.** A [solo class](docs/usage/solo.md) such as `.neobrutalism-solo` takes an intent only from the six built-in classes, so `.neobrutalism-solo.beta` is drawn neutral.
|
|
2015
|
+
- **Hint text.** A [`.hint`](docs/usage/forms.md#show-a-validation-error) prints in an intent's readable tone only for the six built-in classes, so `.hint.beta` stays secondary-colored.
|
|
2016
|
+
- **Table rows.** A body row recolors its text, line, and hover only for the built-in intent classes. `<tr class="beta">` looks like the rest of its table.
|
|
1705
2017
|
|
|
1706
2018
|
If you need those, there are two ways:
|
|
1707
2019
|
|
|
@@ -1814,7 +2126,7 @@ The four `--elevation-*` shadows are for elements you style yourself. They are s
|
|
|
1814
2126
|
| `--elevation-sm` | `0 1px 2px`, `0 1px 3px` | A light shadow for your own surfaces, such as a card. |
|
|
1815
2127
|
| `--elevation-md` | `0 4px 6px -1px`, `0 2px 4px -2px` | A shadow for your own raised elements, such as a dropdown menu. |
|
|
1816
2128
|
| `--elevation-lg` | `0 20px 25px -5px`, `0 8px 10px -6px` | A shadow for your own overlays, such as a modal dialog. |
|
|
1817
|
-
| `--focus-ring` | `--color-primary` at 64% opacity
|
|
2129
|
+
| `--focus-ring` | Not set | Color of the keyboard focus ring. When unset, `--color-primary` at 64% opacity, on the focused element. |
|
|
1818
2130
|
| `--focus-ring-offset` | Not set | Distance of the ring from the element. When unset, the ring sits on the element's border under every aesthetic. |
|
|
1819
2131
|
| `--focus-ring-width` | `3px` | Width of the keyboard focus ring. |
|
|
1820
2132
|
| `--motion-duration-fast` | `120ms` | A table row's hover. |
|
|
@@ -1824,7 +2136,7 @@ The four `--elevation-*` shadows are for elements you style yourself. They are s
|
|
|
1824
2136
|
| `--z-popover` | `40` | Stacking order of tooltip bubbles. Use it for your own popovers so they stack the same way. |
|
|
1825
2137
|
| `--breakpoint-xs` | `30rem` | The `xs:` responsive breakpoint. Only in a `/tw` build; the compiled entrypoints do not declare it. |
|
|
1826
2138
|
|
|
1827
|
-
Each `--elevation-*` shadow is made of the listed parts, each in `--elevation-color`. The hover step that deepens a fill under the pointer is a presentation token, `--hover-step`; see [Customizing](docs/usage/customizing.md#presentation-tokens).
|
|
2139
|
+
Each `--elevation-*` shadow is made of the listed parts, each in `--elevation-color`. The four shadows are composed once, on the root, so an `--elevation-color` you set on a container does not reach them: set it on `:root`, or set the `--elevation-*` token itself on the container. The elevation classes, such as `.card.raised`, read `--elevation-color` on the element and follow a container's value, with one exception: every aesthetic class sets its own `--elevation-color` on its region. A value set above a `.glass` or `.pixel` region does not reach inside it. Set it on the aesthetic's element itself, where your utility or unlayered CSS beats the class, or on an element inside the region. Under glass, a surface's shadow is built on the `.glass` element, so only a value set there changes it. Under `.neobrutalism` the shadow is drawn in the intent or the ink, and `.cyber` draws no depth, so neither reads the token. The hover step that deepens a fill under the pointer is a presentation token, `--hover-step`; see [Customizing](docs/usage/customizing.md#presentation-tokens).
|
|
1828
2140
|
|
|
1829
2141
|
## Fonts
|
|
1830
2142
|
|
|
@@ -1832,14 +2144,14 @@ The package ships no font files and makes no network requests. Every font token
|
|
|
1832
2144
|
|
|
1833
2145
|
| Token | Read by | Without your value |
|
|
1834
2146
|
| ---------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1835
|
-
| `--font-default` | The reset, on `html` and `body`.
|
|
2147
|
+
| `--font-default` | The reset, on `html` and `body`, and `.glass` and `.neobrutalism` regions. Controls inherit it. | `"Segoe UI", system-ui, sans-serif` |
|
|
1836
2148
|
| `--font-mono` | The `kbd`, `code`, and `pre` content helpers, and `<kbd>`, `<code>`, and `<pre>` under `/native` | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`. Under `/tw`, your Tailwind theme's value. |
|
|
1837
2149
|
| `--font-pixel` | [`.pixel`](docs/usage/aesthetics.md#pixel) and `.pixel-solo` | Not declared. Falls back to a monospace stack. |
|
|
1838
2150
|
| `--font-rounded` | [`.chunky-tile`](docs/usage/aesthetics.md#chunky-tile) and `.chunky-tile-solo` | Not declared. Falls back to `--font-default`, then a sans-serif stack. |
|
|
1839
2151
|
| `--font-cyber` | [`.cyber`](docs/usage/aesthetics.md#cyber) and `.cyber-solo` | Not declared. Falls back to a monospace stack. |
|
|
1840
2152
|
| `--font-sketch` | [`.sketch`](docs/usage/aesthetics.md#sketch) and `.sketch-solo` | Not declared. Falls back to the platform `cursive` family. |
|
|
1841
2153
|
|
|
1842
|
-
`--font-default` reaches the page only through the reset, which `@codenhub/styles`, `/native`, `/tw`, `/tw/native`, and `/tw/reset` include. With `/theme` or `/components` alone, set `font-family` on `body` yourself. The aesthetic tokens are read by the aesthetic's own class, so they work with any entrypoint.
|
|
2154
|
+
`--font-default` reaches the page only through the reset, which `@codenhub/styles`, `/native`, `/tw`, `/tw/native`, and `/tw/reset` include. With `/theme` or `/components` alone, set `font-family` on `body` yourself, and set `--font-default` to the same font: `.glass` and `.neobrutalism` regions set their own `font-family` from the token, so a font set only on `body` does not reach inside them. Or set only the token, and write `font-family: var(--font-default)` on `body`. The aesthetic tokens are read by the aesthetic's own class, so they work with any entrypoint.
|
|
1843
2155
|
|
|
1844
2156
|
To use a web font, load it the way you load any font, then name it in the token. Set the token on `:root` for the whole page, or on any element for that part of it:
|
|
1845
2157
|
|
|
@@ -1944,8 +2256,10 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
1944
2256
|
|
|
1945
2257
|
- Content chips take the aesthetic too. A `.kbd` key cap's border changes from the quiet border gray to the ink, and from 1px to 2px, like every other border. A `.code` or `.pre` has no border by default, so it gets none; add `.edged` to give one an ink border.
|
|
1946
2258
|
- Only `.btn` and an opted-in `.card` respond to hover and press. A card opts in with `.interactive`, or with one of its halves — `.hoverable` for the hover response alone, `.pressable` for the press alone; a plain card, an alert, or a panel is a container and stays put.
|
|
1947
|
-
- An alert sits on the offset shadow under this aesthetic, and under no other. An alert is otherwise flat, but here the shadow is a second outline rather than depth, and an alert without it looks unfinished next to everything else. This includes an alert inside a nested `.neobrutalism` region. `.flat` still removes it and `.floating` still doubles it.
|
|
2259
|
+
- An alert sits on the offset shadow under this aesthetic, and under no other. An alert is otherwise flat, but here the shadow is a second outline rather than depth, and an alert without it looks unfinished next to everything else. This includes an alert inside a nested `.neobrutalism` region, and an alert that carries the class itself, `.alert.neobrutalism`. `.flat` still removes it and `.floating` still doubles it.
|
|
1948
2260
|
- The tooltip trigger takes the zero corner like everything else and draws square; `.radio` keeps its circle.
|
|
2261
|
+
- Under `prefers-reduced-motion: reduce` the press still happens, but at once: the element lands in its shadow without travelling there. The press is the feedback that a click registered, so only its motion goes.
|
|
2262
|
+
- It has no font of its own and sets the theme's, `--font-default`, on the region, so a neobrutalist region nested inside pixel, cyber, sketch, or chunky tile does not take their face. A font you set with `font-family` alone, on `body` or a container, does not reach inside the region; set it in `--font-default` too, as [Theming → Fonts](docs/usage/theming.md#fonts) describes.
|
|
1949
2263
|
|
|
1950
2264
|
## Glass
|
|
1951
2265
|
|
|
@@ -1964,8 +2278,9 @@ The shadow is cast in the component's own intent, so a success button throws a g
|
|
|
1964
2278
|
- Every surface in a glass region is glass, a neutral `.card.soft`, `.panel`, and `.alert` included.
|
|
1965
2279
|
- The glass is the ground, not the fill. A presentation keeps its whole fill, so a `.solid` button, a checked toggle, and a `.solid` card or table are opaque; the default, `.soft`, and `.ghost` panes are the frosted ones.
|
|
1966
2280
|
- The blur applies to `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table` only. Controls stay solid and sit on the glass: a blur under every control of a dense cluster costs a composited layer apiece and reads as noise. Controls still take the aesthetic's radius, border, and shadow.
|
|
1967
|
-
- Under `prefers-reduced-transparency: reduce`, glass surfaces drop the blur and become opaque.
|
|
1968
|
-
- Buttons and an opted-in `.card` press with the plain `scale(0.97)`; reduced motion
|
|
2281
|
+
- Under `prefers-reduced-transparency: reduce`, and in forced colors, glass surfaces drop the blur and become opaque. Forced colors replace the colors but keep their transparency, so without this a pane would show the page through the system's background.
|
|
2282
|
+
- Buttons and an opted-in `.card` press with the plain `scale(0.97)`; under reduced motion the press still happens, at once.
|
|
2283
|
+
- It has no font of its own and sets the theme's, `--font-default`, on the region, as neobrutalism does.
|
|
1969
2284
|
- Text controls and toggles draw their line in the page's ink at 55% rather than the white hairline, which would erase an unchecked checkbox on a light page. It is `--ui-control-ink`; set it on the region to choose another.
|
|
1970
2285
|
- A table's frame and rules draw the page's ink at 20%, because the hairline vanishes on the table's plate. It is `--ui-rule-ink`.
|
|
1971
2286
|
- A table with a `<caption>` blurs behind the caption as well as its rows in Chromium and Safari, whose table box includes the caption, and under `.glass-liquid` the rim and sheen frame the caption too. Firefox keeps the caption off the pane. Label the table from a heading outside it with `aria-labelledby` where that matters.
|
|
@@ -1984,13 +2299,13 @@ The lens is drawn by an SVG filter in `backdrop-filter`, which Chromium renders
|
|
|
1984
2299
|
|
|
1985
2300
|
**Exceptions:**
|
|
1986
2301
|
|
|
1987
|
-
- It reaches `.card`, `.panel`, `.alert`, the tooltip bubble, and a default or `.soft` `.data-table`. Controls stay solid, as they do under glass, except a button that asks: see [Liquid buttons](#liquid-buttons).
|
|
2302
|
+
- It reaches `.card`, `.panel`, `.alert`, `.surface`, the tooltip bubble, a component of your own built with `@apply surface`, and a default or `.soft` `.data-table`. Controls stay solid, as they do under glass, except a button that asks: see [Liquid buttons](#liquid-buttons).
|
|
1988
2303
|
- It draws its blur and lens on each surface's `::before` and `::after`, so a surface inside a liquid region cannot carry a `::before` or `::after` of your own. Each surface also becomes a positioned stacking context; a surface you position yourself keeps your placement.
|
|
1989
|
-
- Anything else that frosts under glass keeps liquid's light blur and its clearer ground, without the lens or the rim: a bare `<table>` under `/native
|
|
2304
|
+
- Anything else that frosts under glass keeps liquid's light blur and its clearer ground, without the lens or the rim: a bare `<table>` under `/native`.
|
|
1990
2305
|
- The lens is 8px of erosion blurred over 10px on every surface, and 3px blurred over 3.5px on a button; its strength is fixed. A filter cannot read a custom property, so neither is a knob. The corners are, through `--glass-radius` and `--glass-radius-surface`, with larger defaults: a continuous corner reads smaller than a round one of the same radius.
|
|
1991
|
-
- A region of another aesthetic nested inside a liquid region, or of plain `.glass`, keeps its own material and takes no layers.
|
|
2306
|
+
- The nearest region decides. A region of another aesthetic nested inside a liquid region, or of plain `.glass`, keeps its own material and takes no layers, and a liquid region nested again inside that takes them back.
|
|
1992
2307
|
- Without `.glass` in scope it changes the corners and nothing else.
|
|
1993
|
-
- Under `prefers-reduced-transparency: reduce
|
|
2308
|
+
- Under `prefers-reduced-transparency: reduce`, and in forced colors, the blur and the lens go and the surface is opaque, as under glass.
|
|
1994
2309
|
- Text sits over a clearer pane than under glass. Over busy content, check that your text still reads: the ground is 30% of the page color, where glass's is 45%.
|
|
1995
2310
|
|
|
1996
2311
|
#### Liquid buttons
|
|
@@ -2025,8 +2340,9 @@ A button is a liquid pane when it carries `.glass-liquid` itself, inside a glass
|
|
|
2025
2340
|
**Exceptions:**
|
|
2026
2341
|
|
|
2027
2342
|
- Corners are one unit or nothing. Small chips are square instead of stepped: badges, key caps, inline code, checkboxes, and switches. One unit off each corner of a 24px badge would look like a bite, not a corner.
|
|
2343
|
+
- Surfaces are square too: a `.card`, `.panel`, or `.alert` keeps the inset ring but takes no cut and clips nothing. A clip would cut everything inside it, so a tooltip in a card, a shadow, or a focus ring there would be trimmed.
|
|
2028
2344
|
- Tables, progress bars, and skeletons are square too. A table takes no cut because in Chromium and WebKit its box includes its caption, and the cut would land on the caption instead of the rows. Progress bars and skeletons take only the square corner.
|
|
2029
|
-
- A `.pre` code block is stepped like a
|
|
2345
|
+
- A `.pre` code block is stepped like a button. It has no border by default, so the cut shows with no outline around it.
|
|
2030
2346
|
- The outline is an inset ring, because a clip removes a real border — and the focus ring, for the same reason, is a second inset layer rather than an outline. Both are the element's own edge rather than a shadow, so the border answers `.edged` and `.edgeless` the way a border does: a `.edgeless` badge and a `.solid` button draw none, a `.edged` card draws one, and a field keeps one whatever a container asks for.
|
|
2031
2347
|
- The inset focus ring carries a 2px line of the page color inside it. The ring is the primary color, so on a `.solid.primary` button it would be drawn over its own color; the line is what shows there.
|
|
2032
2348
|
- In forced colors the ring and the cut both go, because the system removes every box shadow: components draw a real 2px border and square corners, and `.pixel-solo` an inward outline.
|
|
@@ -2050,7 +2366,7 @@ The bar is a darker shade of the element's own color rather than a shadow under
|
|
|
2050
2366
|
|
|
2051
2367
|
**Exceptions:**
|
|
2052
2368
|
|
|
2053
|
-
- Only
|
|
2369
|
+
- Only buttons and cards sit on a bar, because they are the components resting above zero elevation. A badge, chip, field, or tooltip is flat until it asks for depth with `.raised`, and `.flat` takes the bar off anything:
|
|
2054
2370
|
|
|
2055
2371
|
```html
|
|
2056
2372
|
<div class="card soft edgeless info flat">Promo panel, deliberately flat</div>
|
|
@@ -2060,7 +2376,8 @@ The bar is a darker shade of the element's own color rather than a shadow under
|
|
|
2060
2376
|
- Labels are drawn heavier and slightly tracked, through `--ui-label-weight` and `--ui-label-tracking`, so your own `font-*` or `tracking-*` utility still wins. It reaches `.btn` and `.badge`, and a bare `<button>` only where `/native` styles it. Casing is left alone: how a label is worded is the application's decision, so if you want uppercase buttons, write that rule in your own stylesheet.
|
|
2061
2377
|
- `.card.interactive` presses like a `.btn`, so a chunky answer tile drops onto its bar on click; `.card.pressable` gets the same press on its own. A plain `.card` stays put.
|
|
2062
2378
|
- The shipped `.primary` is near-black on a light page and near-white on a dark one, and the bar is always mixed toward black. On a light page, a primary button's bar is almost invisible against its near-black face (measured `1.04:1`). On a dark page, the near-white face against the same bar reads well (`2.48:1`). Give `.primary` a hue of its own if it is the call to action and needs the depth cue in both themes.
|
|
2063
|
-
-
|
|
2379
|
+
- Under `prefers-reduced-motion: reduce` the press still happens, but at once, as under neobrutalism.
|
|
2380
|
+
- Depth in this aesthetic is the bar, never a blurred drop shadow. Anything that rests flat sits flush on the page.
|
|
2064
2381
|
- Controls draw a heavier gray than the tiles around them, `neutral-600` in light and `neutral-400` in dark, through `--ui-control-ink`, so a field reads as a field inside a tile.
|
|
2065
2382
|
|
|
2066
2383
|
## Cyber
|
|
@@ -2110,7 +2427,7 @@ Hand-drawn outlines: controls take long, uneven elliptical corners, and cards, p
|
|
|
2110
2427
|
|
|
2111
2428
|
The 1px ink line is solid; checkboxes, dividers, and table rules are dashed. `--ui-line-style: dashed` dashes any other component, and `--ui-rule-style: solid` puts a divider or table back to solid. The 2px hard shadow is a small neutral depth cue.
|
|
2112
2429
|
|
|
2113
|
-
`--sketch-radius`, `--sketch-radius-surface`, and `--sketch-radius-pill` replace the control, surface, and pill-button outlines. Set one and every position takes that one outline. To shape a single component instead, set `--ui-radius` (`--ui-radius-surface` on a surface) on it.
|
|
2430
|
+
`--sketch-radius`, `--sketch-radius-surface`, and `--sketch-radius-pill` replace the control, surface, and pill-button outlines. Set one on the region or on any element inside it, and every position below takes that one outline, on the components that rotate: `.btn`, `.ipt`, `.textarea`, `.select`, `.input-group`, `.pre`, and `.tooltip-bubble` for the control outline, and `.card`, `.panel`, `.alert`, and `.data-table` for the surface one. Any other element that takes a control or surface outline, such as a `.skeleton` or a bare control under `/native`, takes the outline the region itself resolved, so a knob set between the region and that element does not reach it; set the knob on the region instead. Chips, such as `.badge` and `.checkbox`, take small outlines of their own that no knob changes. To shape a single component instead, set `--ui-radius` (`--ui-radius-surface` on a surface) on it.
|
|
2114
2431
|
|
|
2115
2432
|
```html
|
|
2116
2433
|
<section class="sketch" style='--font-sketch: "Comic Sans MS", cursive'>
|
|
@@ -2133,11 +2450,12 @@ The dashed button is `ghost edged` because a filled button has no separate line
|
|
|
2133
2450
|
|
|
2134
2451
|
**Exceptions:**
|
|
2135
2452
|
|
|
2136
|
-
- Sketch names components to rotate their outlines, so its outlines reach only this package's components; a region of another aesthetic nested inside a sketch region keeps that aesthetic's corners.
|
|
2453
|
+
- Sketch names components to rotate their outlines, so its outlines reach only this package's components; a region of another aesthetic nested inside a sketch region keeps that aesthetic's corners. The nearest region decides, so a sketch region nested again inside that one rotates.
|
|
2137
2454
|
- Chips, such as checkboxes, key caps, code, badges, switches, and progress bars, rotate through small uneven outlines set in `--ui-radius-tight`. Set that token on a chip to choose its shape.
|
|
2138
2455
|
- A `.btn.pill` takes a rounded pill bounded to its own height, so a wide one does not become an oval. Badges, progress bars, and switches take their chip outline. Radios and the tooltip icon stay circles, slightly uneven, so a radio is never mistaken for a checkbox. An explicit `--ui-radius-pill` on an element still wins.
|
|
2139
2456
|
- `--font-sketch` is yours to supply. The package ships no handwriting font and falls back to the platform `cursive` family.
|
|
2140
|
-
- Buttons press with the plain `scale(0.97)`; reduced motion
|
|
2457
|
+
- Buttons press with the plain `scale(0.97)`; under reduced motion the press still happens, at once.
|
|
2458
|
+
- With [`/native`](docs/setup.md#classless-html), a plain `<hr>`, `<table>`, and checkbox take the dashed accents of `.divider`, `.data-table`, and `.checkbox`.
|
|
2141
2459
|
- Forced-colors mode draws the dashed accents solid, like every other material line.
|
|
2142
2460
|
|
|
2143
2461
|
## Solo classes
|
|
@@ -2222,7 +2540,9 @@ With none of those classes, a solo class paints the material alone (edge, corner
|
|
|
2222
2540
|
## How it wins and when it moves
|
|
2223
2541
|
|
|
2224
2542
|
- **It beats the other library's styles.** The rules are unlayered and one class deep, so they win over a component's layered rules, and over its zero-specificity ones, wherever the two load. An unlayered foreign rule that is more specific, or as specific and later in the page, still wins. The other side of this is that a Tailwind utility on the same element, such as `rounded-none`, loses to the solo class: tune it through its knobs instead.
|
|
2225
|
-
- **It presses only an action.** Neobrutalism, chunky tile, and cyber press a `button`, `a[href]`, `[role="button"]`, `summary`, or button-type `input`
|
|
2543
|
+
- **It presses only an action.** Neobrutalism, chunky tile, and cyber press a `button`, `a[href]`, `[role="button"]`, `summary`, or button-type `input` unless it carries the `disabled` attribute, `.disabled`, `aria-disabled="true"`, or `data-disabled`. Only the attribute disables a button; the press ignores the other three too, since a page uses them to mark one it handles as disabled. Anything else stays put, so a toast does not sink when clicked. Chunky tile's heavier label follows the same rule.
|
|
2544
|
+
- **It leaves a container's motion alone.** Only an action takes the press's `transition`, and the press moves the element through the `translate` and `scale` properties, not `transform`. A toast keeps its own transitions and its own `transform`. On an action, the press's `transition` replaces any the element had.
|
|
2545
|
+
- **Reduced motion keeps the press and drops its motion.** Under `prefers-reduced-motion: reduce`, neobrutalism, chunky tile, and cyber still press, but at once: the element lands in its shadow or bar, or shrinks, without travelling there.
|
|
2226
2546
|
|
|
2227
2547
|
## Next
|
|
2228
2548
|
|
|
@@ -2248,6 +2568,9 @@ This is a lookup page. [Building a screen](docs/concepts.md) introduces intent,
|
|
|
2248
2568
|
- **Aesthetics and [material tokens](docs/usage/customizing.md#material-tokens) pass down** the same way.
|
|
2249
2569
|
- **Intent does not pass down.** `.primary` and the other intent classes color only the element they are on. A container that silently recolored everything inside it would turn a red danger-zone card's neutral Cancel button red. `.neutral` names the default; use it where an element would otherwise take an intent, such as a table row inside a `.success` table. See [Theming → Intent tokens](docs/usage/theming.md#intent-tokens).
|
|
2250
2570
|
- **Elevation does not pass down.** `.raised` lifts only the element it is on; see [Elevation](#elevation).
|
|
2571
|
+
- **Size does not pass down.** `.sm` and `.lg` resize only the button or control they are on, so a `.sm` form does not shrink the fields inside it. See [Buttons → Sizes and shapes](docs/usage/buttons.md#sizes-and-shapes) and [Forms → Sizes](docs/usage/forms.md#sizes).
|
|
2572
|
+
|
|
2573
|
+
With the Tailwind source entrypoints, every class above except the aesthetic classes is also a Tailwind utility, so variants such as `md:lg` and `dark:solid` work and `@apply btn primary soft sm` gives one class of yours the whole combination. The modifiers documented with each component, such as `.tight` or `.interactive`, are utilities too, so `@apply stack tight` works. The aesthetic classes, such as `.glass`, stay plain CSS, so `@apply glass` and `md:glass` do not work; write the class in the markup. See [Tailwind CSS v4 → Build your own components with `@apply`](docs/integrating/tailwind.md#build-your-own-components-with-apply), and [Presets](docs/usage/presets.md) for when to reach for it.
|
|
2251
2574
|
|
|
2252
2575
|
## Component defaults
|
|
2253
2576
|
|
|
@@ -2303,6 +2626,7 @@ A neutral `.card.soft`, `.panel`, and `.alert` show no tint at rest. They use th
|
|
|
2303
2626
|
|
|
2304
2627
|
- A `.card.soft` with `.interactive` or `.hoverable` shows the tint on hover. A plain `.card.soft` and a `.panel` have no hover state.
|
|
2305
2628
|
- With one of the six built-in intents, all three keep the ordinary 12% tint, and a card deepens it on hover. An intent you defined yourself is treated as neutral here; see [Theming → Your own intent](docs/usage/theming.md#your-own-intent).
|
|
2629
|
+
- The presentation can come from the element or from a container: `<div class="soft"><div class="card">` rests like `.card.soft`, and a `.ghost` or `.solid` container gives a `.panel` or `.alert` the plate its own class would.
|
|
2306
2630
|
- A `.badge` keeps its 12% tint even when neutral. It has no border, so without the tint it would disappear into the text around it.
|
|
2307
2631
|
|
|
2308
2632
|
`.kbd`, `.code`, and `.pre` work differently. They rest on their intent's subtle tone, `--intent-subtle`, which is `--color-surface` for neutral, and a built-in intent adds 12% of its color over it. An intent you defined yourself shows its own `--intent-subtle` tint without that extra 12%.
|
|
@@ -2364,7 +2688,7 @@ Which classes change each component. "Material" is what an [aesthetic](docs/usag
|
|
|
2364
2688
|
| `.progress` | Track fill, border, and border width. The filled part is always the full intent color. |
|
|
2365
2689
|
| `.skeleton`, `.divider` | Intent only. |
|
|
2366
2690
|
|
|
2367
|
-
State and modifier classes such as `.interactive`, `.hoverable`, `.pressable`, `.p-sm`, and `.vertical` are documented with their component. An invalid control is marked with `aria-invalid="true"`, not a class; see [Forms](docs/usage/forms.md#show-a-validation-error).
|
|
2691
|
+
State and modifier classes such as `.interactive`, `.hoverable`, `.pressable`, `.p-sm`, and `.vertical` are documented with their component. Each takes effect on the components that read it, and on `/tw` they are utilities, so `@apply card interactive` works. An invalid control is marked with `aria-invalid="true"`, not a class; see [Forms](docs/usage/forms.md#show-a-validation-error).
|
|
2368
2692
|
|
|
2369
2693
|
## Elevation
|
|
2370
2694
|
|
|
@@ -2397,7 +2721,7 @@ This is a lookup page for changing the package beyond its classes. [Theming](doc
|
|
|
2397
2721
|
- [Presentation tokens](#presentation-tokens): how much of an intent a component shows.
|
|
2398
2722
|
- [Material tokens](#material-tokens): what a component is made of.
|
|
2399
2723
|
- [Aesthetic tokens](#aesthetic-tokens): the knobs each aesthetic exposes.
|
|
2400
|
-
- [Component variables](#component-variables): the
|
|
2724
|
+
- [Component variables](#component-variables): the public variables a single component reads.
|
|
2401
2725
|
- [Generated palette](#generated-palette): fixed color values for code that cannot load the package.
|
|
2402
2726
|
- [Composition helpers](#composition-helpers): the classes to build a component of your own from.
|
|
2403
2727
|
|
|
@@ -2461,7 +2785,7 @@ Shadow geometry is split into parts with no color in them, so they inherit safel
|
|
|
2461
2785
|
| `--ui-hover-shadow-y` | Shadow vertical offset while hovered. | `--ui-shadow-y` |
|
|
2462
2786
|
| `--ui-active-shadow-x` | Shadow horizontal offset while pressed. | `--ui-shadow-x` |
|
|
2463
2787
|
| `--ui-active-shadow-y` | Shadow vertical offset while pressed. | `--ui-shadow-y` |
|
|
2464
|
-
| `--ui-active-transform` | Transform while pressed.
|
|
2788
|
+
| `--ui-active-transform` | Transform while pressed. Kept under `prefers-reduced-motion`, which drops only its transition. | `scale(0.97)` |
|
|
2465
2789
|
| `--ui-active-translate-x` | Distance a press travels into the element's depth; `-y` beside it. Scaled like the shadow. | `0px` |
|
|
2466
2790
|
| `--ui-shadow-ink` | Percentage of shadow color taken from intent ink. | `0%` |
|
|
2467
2791
|
| `--ui-halo-blur` | Blur of a halo around the element; `--ui-halo-spread` beside it. Elevation does not scale it. | `0px` |
|
|
@@ -2478,7 +2802,7 @@ Shadow geometry is split into parts with no color in them, so they inherit safel
|
|
|
2478
2802
|
| `--ui-clip-tight` | Compact component silhouette. | `--ui-clip` |
|
|
2479
2803
|
| `--ui-focus-inset` | Inset focus layer width. | Unset |
|
|
2480
2804
|
| `--ui-label-weight` | Label weight of `.btn` (and, with `/native`, a bare `<button>`) and `.badge`. | Semibold on a button, bold on a badge |
|
|
2481
|
-
| `--ui-label-tracking` | Label letter spacing. Unset, the
|
|
2805
|
+
| `--ui-label-tracking` | Label letter spacing. Unset, it is `normal`, so neither the page's tracking nor a container's reaches the label. | `normal` |
|
|
2482
2806
|
| `--ui-label-case` | Label `text-transform`. | `none` on a button; inherited on a badge |
|
|
2483
2807
|
| `--ui-label-shadow` | Label `text-shadow`, written without a color so it takes the label's own. | `none` on a button; inherited on a badge |
|
|
2484
2808
|
|
|
@@ -2505,7 +2829,7 @@ Which corners take the corner is its own choice. `.cut-diagonal` keeps the top-l
|
|
|
2505
2829
|
|
|
2506
2830
|
`--ui-surface-image` lays any `background-image` over a surface's plate: cards, panels, alerts, and tooltips, not controls. Whoever sets it owns the contrast of the text over it, and your own `background-image` on a surface still wins.
|
|
2507
2831
|
|
|
2508
|
-
`--ui-label-*` treats the label of a button or a badge: weight, tracking, case, and a text shadow. Write the shadow without a color, such as `0 0 6px`, so it takes the label's own color and cannot lower its contrast. Your own `font-*`, `tracking-*`, and case utilities still win.
|
|
2832
|
+
`--ui-label-*` treats the label of a button or a badge: weight, tracking, case, and a text shadow. Write the shadow without a color, such as `0 0 6px`, so it takes the label's own color and cannot lower its contrast. Your own `font-*`, `tracking-*`, and case utilities still win. A label does not inherit tracking, so a `tracking-*` utility on a container leaves its buttons and badges at `normal`; to track every label in a region, set `--ui-label-tracking` on it, which they inherit.
|
|
2509
2833
|
|
|
2510
2834
|
Under forced colors each of these draws its plain version: a solid line, no halo, no painted layer, and no label shadow. The silhouette and the ring below go too. Forced colors remove every box shadow, so `--ui-clip`, `--ui-clip-tight`, `--ui-border-max`, and `--ui-shadow-edge` are cleared and a component draws its real border at `--ui-border-width`.
|
|
2511
2835
|
|
|
@@ -2581,9 +2905,9 @@ Every aesthetic's knobs reach the same way: each knob is _read_ with its default
|
|
|
2581
2905
|
</html>
|
|
2582
2906
|
```
|
|
2583
2907
|
|
|
2584
|
-
Knobs and material tokens reach in opposite directions. A knob is read once, at the element with the aesthetic class, and the result passes down. So set a knob on that element or on one above it; set on an element inside the aesthetic, it changes nothing, because the value has already been read.
|
|
2908
|
+
Knobs and material tokens reach in opposite directions. A knob is read once, at the element with the aesthetic class, and the result passes down. So set a knob on that element or on one above it; set on an element inside the aesthetic, it changes nothing, because the value has already been read. The sketch outlines are the exception: `--sketch-radius` and its siblings are read again at each position, so they work on any element inside the region too, as [Aesthetics → Sketch](docs/usage/aesthetics.md#sketch) describes.
|
|
2585
2909
|
|
|
2586
|
-
`--ui-radius
|
|
2910
|
+
`--ui-radius`, `--ui-radius-surface`, and the other material tokens are read by each component as it draws. They work on the element they are set on and everything inside it, which is how you round one card differently from its neighbors without touching the knob. Mind which one a component reads: a surface (`.card`, `.panel`, `.alert`) takes `--ui-radius-surface`, and a control such as `.btn` or `.ipt` takes `--ui-radius`. `style="--ui-radius-surface: 0"` squares one card; `--ui-radius` on that card squares the buttons inside it and leaves the card round.
|
|
2587
2911
|
|
|
2588
2912
|
Shipped aesthetics set the shared material tokens above. Every shipped aesthetic sets `--ui-ink` for its neutral line. Every shipped aesthetic also sets `--ui-control-ink`: `.glass` and `.chunky-tile` to a line of their own, heavier than their surfaces', and the others to `--color-control-border`, since their ink is near-black or near-white and a resting checkbox outlined in it would look checked. `.glass` sets `--ui-rule-ink` too, so a table's lines read on its plate; the others clear it. Intent classes still override the intent slots on each component.
|
|
2589
2913
|
|
|
@@ -2601,9 +2925,9 @@ The same knobs tune each aesthetic's [solo class](docs/usage/solo.md), which rea
|
|
|
2601
2925
|
|
|
2602
2926
|
```css
|
|
2603
2927
|
.my-badge {
|
|
2604
|
-
background: var(--palette-success-soft-
|
|
2928
|
+
background: var(--palette-success-soft-bg);
|
|
2605
2929
|
color: var(--palette-success-soft-fg);
|
|
2606
|
-
border-color: var(--palette-success-soft-
|
|
2930
|
+
border-color: var(--palette-success-soft-edge);
|
|
2607
2931
|
}
|
|
2608
2932
|
```
|
|
2609
2933
|
|
|
@@ -2612,29 +2936,46 @@ Every value is a finished color, never a reference to another variable, so the f
|
|
|
2612
2936
|
Naming: `--palette-<intent>-<presentation>-<slot>`, where `<intent>` is one of the seven published intents (`neutral`, `primary`, `secondary`, `success`, `warning`, `destructive`, `info`) and `<presentation>` is `solid`, `soft`, or `ghost`. `<slot>` is `bg`, `fg`, or `edge`. Two qualifiers layer on only where they apply:
|
|
2613
2937
|
|
|
2614
2938
|
- A ground qualifier, `page` or `subtle`, before the slot. It appears only on the `bg` and `edge` of `.soft` and `.ghost`, because a `.solid` fill covers its ground and the text color never depends on it.
|
|
2615
|
-
- No qualifier is
|
|
2616
|
-
- `page` is
|
|
2617
|
-
- `subtle` is
|
|
2618
|
-
|
|
2939
|
+
- No qualifier is what `.badge` and `.btn` draw, on a transparent ground: `--palette-success-soft-bg`.
|
|
2940
|
+
- `page` is what `.alert` draws, on the page: `--palette-success-soft-page-bg`.
|
|
2941
|
+
- `subtle` is what `.kbd`, `.code`, `.pre`, and `.tooltip-icon` draw, on the intent's own subtle tone: `--palette-success-soft-subtle-bg`.
|
|
2942
|
+
|
|
2943
|
+
Each value is read from the component itself, so it includes what the component does on its own. A neutral `.alert`, `.kbd`, `.code`, or `.pre` keeps a quieter fill than a neutral `.badge`, and its `neutral-soft-page` and `neutral-soft-subtle` values say so. A neutral `.tooltip-icon.soft` keeps the badge's fill, so its plate stands further from the page than `neutral-soft-subtle` says. `.tooltip-bubble` matches none of the three: with no intent it fills with its own tooltip tone, and its edge is solid rather than faded.
|
|
2944
|
+
|
|
2945
|
+
- A `-hover` suffix, on `bg` and `edge` only, because the text color does not change on hover: `--palette-success-soft-page-bg-hover`. `.alert`, `.kbd`, `.code`, and `.pre` have no hover of their own, so their hover values are what the component draws when you add `box-hover` to it.
|
|
2619
2946
|
|
|
2620
2947
|
Light values are set on `:root`. Dark values apply under `.dark`, `.theme-dark`, or `[data-theme="dark"]` on the element or an element above it, the same selectors the package's theme uses. The light values also apply under `.light`, `.theme-light`, and `[data-theme="light"]`, so the nearest explicit theme wins: a light area inside a dark one reads the light values.
|
|
2621
2948
|
|
|
2622
2949
|
The file does not follow `prefers-color-scheme`. Code that uses the palette on its own already switches themes itself, so it sets one of those classes or attributes.
|
|
2623
2950
|
|
|
2951
|
+
Nor does it follow `prefers-contrast: more`. Under that setting the package draws each intent's border in its strong tone, so a `-edge` value is fainter than the border the component draws. Code that has to honor the setting picks a stronger color itself.
|
|
2952
|
+
|
|
2624
2953
|
Three more tokens have no intent, presentation, ground, or hover: `--palette-border`, `--palette-surface`, and `--palette-text`, fixed values of `--color-border`, `--color-surface`, and `--color-text`. They are for things that are not intent-colored, such as a container's border, a dialog's background, and its text.
|
|
2625
2954
|
|
|
2626
2955
|
## Component variables
|
|
2627
2956
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
| Variable
|
|
2631
|
-
|
|
|
2632
|
-
| `--progress-value`
|
|
2633
|
-
| `--progress-color`
|
|
2634
|
-
| `--progress-surface`
|
|
2635
|
-
| `--progress-edge`
|
|
2636
|
-
| `--input-group-gap`
|
|
2637
|
-
| `--
|
|
2957
|
+
Eighteen variables are read by a single component and are supported inputs. A component reads each one where it is drawn, so a value set on the component or on any element around it reaches it, and the nearest value wins: set `--switch-width` on a form and every switch in it takes it. Every other variable a component declares, such as `--surface-padding` or any name that starts with `--_`, is internal and may change in any release.
|
|
2958
|
+
|
|
2959
|
+
| Variable | Element | Value | Default | Effect |
|
|
2960
|
+
| ----------------------- | ----------------------------- | ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
2961
|
+
| `--progress-value` | `.progress` | A percentage, `0%` to `100%` | `0%` | Width of the filled part. Not clamped; a value without `%` is invalid and shows empty. |
|
|
2962
|
+
| `--progress-color` | `.progress` | A color | The intent color | Color of the filled part and of the `.active` shimmer. |
|
|
2963
|
+
| `--progress-surface` | `.progress` | A color | The intent color at the presentation's fill: 12% by default | Color of the track. |
|
|
2964
|
+
| `--progress-edge` | `.progress` | A color | The intent's border color at the presentation's border: none by default | Color of the track's border. It shows only where the track has a border width, such as with `.edged`. |
|
|
2965
|
+
| `--input-group-gap` | `.input-group` | A length | `0.5rem` | Space between the control and its icon or text. |
|
|
2966
|
+
| `--input-group-padding` | `.input-group` | A length | `0.75rem`; `0.625rem` with `.sm` and `1rem` with `.lg` | Space between the group's edge and its content, on both sides. A value you set replaces the `.sm` and `.lg` steps too. |
|
|
2967
|
+
| `--field-gap` | `.field` | A length | `0.35rem` | Space between a field's label, control, and hint. |
|
|
2968
|
+
| `--switch-width` | `.switch` | A length | `2.5rem` | Width of the track. The knob keeps its size and travels further. |
|
|
2969
|
+
| `--switch-height` | `.switch` | A length | `1.25rem` | Height of the track. The knob grows with it. |
|
|
2970
|
+
| `--tooltip-gap` | `.tooltip` | A length | `0.5rem` | Space between the trigger and the bubble. |
|
|
2971
|
+
| `--tooltip-linger` | `.tooltip`, `.tooltip-bubble` | A time | `300ms` | How long the bubble stays after the pointer leaves the trigger. |
|
|
2972
|
+
| `--divider-color` | `.divider` | A color | The intent color | Color of the line. It wins over an intent class on the divider. |
|
|
2973
|
+
| `--divider-width` | `.divider` | A length | The border width: `1px` by default | Thickness of the line. |
|
|
2974
|
+
| `--skeleton-base` | `.skeleton` | A color | A faint tint of the intent color | Color the placeholder rests on. |
|
|
2975
|
+
| `--skeleton-sheen` | `.skeleton` | A color | A stronger tint of the intent color | Color of the highlight that sweeps across it. |
|
|
2976
|
+
| `--quote-width` | `.quote` | A length | Twice the border width plus `1px`: `3px` by default | Thickness of the bar on the quotation's starting edge. |
|
|
2977
|
+
| `--table-cell-padding` | `.data-table` | A `padding` value | `0.75rem 1rem` | Padding of every header, body, and footer cell. A padding utility on a cell still wins. |
|
|
2978
|
+
| `--table-hover` | `.data-table` | A color | A 12% step toward the intent's hover color | Background of a body row under the pointer. `transparent` turns it off. Set on a `tbody` or a row, it applies there only. |
|
|
2638
2979
|
|
|
2639
2980
|
```html
|
|
2640
2981
|
<div class="progress" role="meter" aria-label="Storage used" aria-valuemin="0" aria-valuemax="100" aria-valuenow="92" style="--progress-value: 92%; --progress-color: var(--color-warning)"></div>
|
|
@@ -2649,7 +2990,7 @@ The examples in context are in [Feedback → Show progress](docs/usage/feedback.
|
|
|
2649
2990
|
|
|
2650
2991
|
## Composition helpers
|
|
2651
2992
|
|
|
2652
|
-
To build a component the package does not ship, such as a segmented control or a filter chip, compose it from the same classes the package's components are built from. With compiled CSS, write them as classes; with Tailwind source, you can also `@apply` them.
|
|
2993
|
+
To build a component the package does not ship, such as a segmented control or a filter chip, compose it from the same classes the package's components are built from. With compiled CSS, write them as classes; with Tailwind source, you can also `@apply` them, together with the intent, presentation, elevation, and corner classes, which are utilities on `/tw` too: `@apply box box-hover neutral soft edgeless`. To give a shipped component a fixed recipe instead, such as a small soft primary button, see [Tailwind CSS v4 → Build your own components with `@apply`](docs/integrating/tailwind.md#build-your-own-components-with-apply).
|
|
2653
2994
|
|
|
2654
2995
|
| Class | What it gives an element |
|
|
2655
2996
|
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -2672,7 +3013,10 @@ Filter chips for the members list, one pressed and one not:
|
|
|
2672
3013
|
What to give each one:
|
|
2673
3014
|
|
|
2674
3015
|
- **`box` needs a fill and an edge class.** It has no defaults of its own, so with no `.solid`, `.soft`, or `.ghost` and no `.edged` or `.edgeless`, on the element or a container, it draws nothing. `surface` and `text-control` have defaults and work alone.
|
|
2675
|
-
- **Name the intent
|
|
3016
|
+
- **Name the intent on `box`.** A `box` element takes the intent of the nearest element around it that has one, unlike the package's components. Put `.neutral` or an intent class on it. `surface` and `text-control` are neutral unless the element has an intent class, as components are.
|
|
3017
|
+
- **`@apply` carries the whole helper.** `@utility my-field { @apply text-control; }` gets what `class="text-control"` gets: the neutral intent and the field border, `.soft`, `.solid`, and `.edgeless` on the element, and the invalid look for `aria-invalid="true"`. `@apply surface` is neutral the same way. An intent class you defined yourself must be plain CSS or a utility to color such an element, not a rule in `@layer components`.
|
|
3018
|
+
- **Write the class that applies a helper as a utility.** What `@apply` copies lands in the [cascade layer](docs/setup.md#cascade-layers) of the rule it is written in. The helpers are utilities, and `@utility my-field` keeps your class in `utilities` beside them, so your other utilities still reach it. Written as plain CSS, `.my-field { @apply text-control; }` is unlayered, and everything the helper declares beats every utility on the element: an intent class you wrote as a utility leaves the field neutral, and `mt-8` does not move a dialog. Your own unlayered CSS still contests it, by specificity and then source order, as any two unlayered rules do.
|
|
3019
|
+
- **A dialog works with `@apply surface`.** A closed `<dialog>` with your own `@apply surface` class stays hidden, and an open one is centered, as with `.card`. Hiding a closed one wins over a display utility such as `flex`; a margin you set still places an open one, and a margin utility does when your class is a utility too.
|
|
2676
3020
|
- **Size and spacing are yours.** None of the helpers sets padding, width, or height; `text-control` does not set the 2.5rem height that `.ipt` adds.
|
|
2677
3021
|
- **So are behavior and semantics.** Use a real `<button>`, `<input>`, or `<a href>`, or add the role and keyboard handling yourself, and set states such as `aria-pressed` or `aria-invalid`. `box` draws the disabled look for `disabled`, `.disabled`, `aria-disabled="true"`, and `data-disabled`, but only the `disabled` attribute stops input.
|
|
2678
3022
|
|
|
@@ -2725,7 +3069,7 @@ A `.divider` takes an intent, which colors it, and ignores presentation classes,
|
|
|
2725
3069
|
|
|
2726
3070
|
The two largest sizes grow at Tailwind's `md` breakpoint, 48rem by default. With the Tailwind source entrypoints, your own `md` breakpoint applies. The package sets no size on unclassed text: body text and unclassed headings take the browser's size, or the surrounding text size under the reset; see [Setup → Classless HTML](docs/setup.md#classless-html) for the classless mapping.
|
|
2727
3071
|
|
|
2728
|
-
`.text-body` is 0.875rem, smaller than the 1rem page text; use it for secondary or dense copy, not for the page's main text.
|
|
3072
|
+
`.text-body` is 0.875rem, smaller than the 1rem page text; use it for secondary or dense copy, not for the page's main text. `/native` is the exception: it sets `.text-body` on `body`, so there it is the page text, and paragraphs and list items inherit it from whatever they sit in. See [Classless HTML](docs/setup.md#classless-html).
|
|
2729
3073
|
|
|
2730
3074
|
`.selection-contrast` on an element swaps the colors of selected text inside it to `--color-primary` on `--color-primary-contrast`. Use it on a `.solid.primary` area, where the default selection color would match the background.
|
|
2731
3075
|
|
|
@@ -2768,7 +3112,7 @@ A neutral `.card.soft` and a neutral `.panel` show no tint: they rest on the qui
|
|
|
2768
3112
|
| `.hoverable` | The hover color only, with no pointer cursor. |
|
|
2769
3113
|
| `.pressable` | The press and the pointer cursor only. |
|
|
2770
3114
|
|
|
2771
|
-
On the default look, a press scales the card down slightly (`scale(0.97)`); an aesthetic can replace that with its own press, and a reduced-motion preference
|
|
3115
|
+
On the default look, a press scales the card down slightly (`scale(0.97)`); an aesthetic can replace that with its own press, and under a reduced-motion preference it still happens, at once.
|
|
2772
3116
|
|
|
2773
3117
|
Use one half for a card that answers only one gesture. A card that holds its own button takes `.hoverable`, so that pressing the button does not press the whole card.
|
|
2774
3118
|
|
|
@@ -2783,6 +3127,8 @@ Use one half for a card that answers only one gesture. A card that holds its own
|
|
|
2783
3127
|
|
|
2784
3128
|
These classes only style. Put them on a real link or button and give it an accessible name; a `<div class="card interactive">` cannot be focused or used from a keyboard.
|
|
2785
3129
|
|
|
3130
|
+
A disabled `.interactive` or `.pressable` card shows the not-allowed cursor instead of the pointer, whether it is a `<button>` with `disabled`, or carries `.disabled`, `aria-disabled="true"`, or `data-disabled`.
|
|
3131
|
+
|
|
2786
3132
|
## Tables
|
|
2787
3133
|
|
|
2788
3134
|
The settings screen lists API keys in a table:
|
|
@@ -2827,6 +3173,8 @@ The settings screen lists API keys in a table:
|
|
|
2827
3173
|
|
|
2828
3174
|
A table's rows take the table's intent, so `.data-table.success` colors its header and lines green. A row with its own intent class uses that instead, as the revoked key's `<tr class="destructive">` does. `<tr class="neutral">` sets a row back to neutral inside a colored table. On a `.solid` table, a row with its own intent keeps the table's text color so it stays readable on the table's fill, and shows its intent through its row line and hover. This is the one place an intent passes down, because rows are parts of the table, not separate components.
|
|
2829
3175
|
|
|
3176
|
+
Only the six built-in intents and `.neutral` recolor a body row. A row with an intent you defined yourself, such as `<tr class="beta">`, looks like the rest of the table: its text, line, and hover come from the table's intent. To mark such a row, write a built-in intent class on the row and retheme that intent's color family on the row, as [Theming → Your own intent](docs/usage/theming.md#your-own-intent) describes, or paint the row with your own utilities.
|
|
3177
|
+
|
|
2830
3178
|
### Table rules
|
|
2831
3179
|
|
|
2832
3180
|
A table draws lines under its header and above its footer. `.ruled` and `.ruleless` change them together with the lines between body rows:
|
|
@@ -2839,6 +3187,8 @@ A table draws lines under its header and above its footer. `.ruled` and `.rulele
|
|
|
2839
3187
|
|
|
2840
3188
|
`--ui-rule` is the token behind both classes, so a container can rule every table inside it: `style="--ui-rule: 100%"` acts like `.ruled`, and `0%` like `.ruleless`.
|
|
2841
3189
|
|
|
3190
|
+
Two variables and a token tune the rest, on the table or on any element around it. `--table-cell-padding` sets the padding of every cell, so `--table-cell-padding: 0.375rem 0.75rem` makes a dense table. `--table-hover` sets the color of a hovered body row, and `transparent` turns the hover off. `--ui-rule-ink` sets the color of a neutral table's lines; a table with an intent draws them in the intent's color. See [Customizing → Component variables](docs/usage/customizing.md#component-variables) for the two variables and [Material tokens](docs/usage/customizing.md#material-tokens) for `--ui-rule-ink`.
|
|
3191
|
+
|
|
2842
3192
|
The frame around the table is separate: a table has no border by default, and `.edged` adds one. The three choices do not affect each other, so `.data-table.ruleless.edged` is a framed table with no lines inside, and `.data-table.ruled` has every row separated and no frame.
|
|
2843
3193
|
|
|
2844
3194
|
### Presentation on tables
|
|
@@ -2891,6 +3241,94 @@ A `.kbd`, `.code`, or `.pre` rests on its intent's subtle tone, `--color-surface
|
|
|
2891
3241
|
|
|
2892
3242
|
## Next
|
|
2893
3243
|
|
|
3244
|
+
[Presets](docs/usage/presets.md) shows how to set a look once instead of on every element. [Accessibility](docs/accessibility.md) lists what the CSS does for your users and what your application still owes them.
|
|
3245
|
+
|
|
3246
|
+
<!-- Source: docs/usage/presets.md -->
|
|
3247
|
+
|
|
3248
|
+
# Presets
|
|
3249
|
+
|
|
3250
|
+
By now the settings screen says `btn primary soft sm` in a dozen places, and changing your mind means changing a dozen places. This page covers the three ways to set a look once: axis classes on a container, theme and material tokens, and your own component classes built with `@apply`. Each fits a different kind of "once".
|
|
3251
|
+
|
|
3252
|
+
## Which one to use
|
|
3253
|
+
|
|
3254
|
+
| You want | Reach for |
|
|
3255
|
+
| --------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
3256
|
+
| Every component in one region to share a fill, an edge, or a material | [Axis classes on a container](#axis-classes-on-a-container) |
|
|
3257
|
+
| Different colors, corners, borders, or depth for a page or a region | [Theme and material tokens](#theme-and-material-tokens) |
|
|
3258
|
+
| One name for a recipe you repeat, such as "the save button" | [Your own component classes](#your-own-component-classes) |
|
|
3259
|
+
|
|
3260
|
+
They combine. A region can set a material token, sit under an aesthetic, and hold your own button class, and each element can still override what it inherits.
|
|
3261
|
+
|
|
3262
|
+
## Axis classes on a container
|
|
3263
|
+
|
|
3264
|
+
Some classes work on a container as well as on a component, and everything inside takes them:
|
|
3265
|
+
|
|
3266
|
+
- The presentation classes: `.solid`, `.soft`, `.ghost`, `.edged`, and `.edgeless`.
|
|
3267
|
+
- The corner patterns: `.cut-diagonal` and `.cut-diagonal-reverse`.
|
|
3268
|
+
- The [aesthetics](docs/usage/aesthetics.md), such as `.glass` or `.pixel`.
|
|
3269
|
+
|
|
3270
|
+
```html
|
|
3271
|
+
<div class="cluster soft edged">
|
|
3272
|
+
<button class="btn primary" type="button">Bold</button>
|
|
3273
|
+
<button class="btn" type="button">Italic</button>
|
|
3274
|
+
<button class="btn destructive solid" type="button">Clear formatting</button>
|
|
3275
|
+
</div>
|
|
3276
|
+
```
|
|
3277
|
+
|
|
3278
|
+
The toolbar's buttons are soft and edged, and the last one asks for its own fill. A class on the element always beats one from a container.
|
|
3279
|
+
|
|
3280
|
+
The other axes do not pass down. Intent, elevation (`.flat`, `.raised`, `.floating`), and size (`.sm`, `.lg`) change only the element they are on, so a `.sm` toolbar does not shrink its buttons; [Composing → Combining classes](docs/usage/composing.md#combining-classes) explains why. An aesthetic class also sets its own corner pattern on its region, so a `.cut-diagonal` above a `.pixel` region does not reach inside it.
|
|
3281
|
+
|
|
3282
|
+
Use a container class when the look belongs to the region, such as a quiet sidebar, rather than to the components in it.
|
|
3283
|
+
|
|
3284
|
+
## Theme and material tokens
|
|
3285
|
+
|
|
3286
|
+
Tokens are CSS custom properties. Set one on `:root` and the whole page follows; set it on an element and only what is inside follows. They reach further than classes, because components read them while they draw:
|
|
3287
|
+
|
|
3288
|
+
```css
|
|
3289
|
+
.admin {
|
|
3290
|
+
--radius-control: 0.25rem;
|
|
3291
|
+
--radius-surface: 0.5rem;
|
|
3292
|
+
--border-width: 2px;
|
|
3293
|
+
}
|
|
3294
|
+
```
|
|
3295
|
+
|
|
3296
|
+
Every button, field, tooltip bubble, and surface inside `.admin` takes the tighter corners and the heavier border, with no class on any of them. Checkboxes, `.kbd`, and `.code` keep their own smaller corner, `--radius-small`.
|
|
3297
|
+
|
|
3298
|
+
The tables live where the tokens are explained, so this page does not repeat them:
|
|
3299
|
+
|
|
3300
|
+
- [Theming → Color tokens](docs/usage/theming.md#color-tokens): brand and status colors, in both themes.
|
|
3301
|
+
- [Theming → Foundation tokens](docs/usage/theming.md#foundation-tokens): corners, border width, control height, focus ring, depth, and motion.
|
|
3302
|
+
- [Customizing → Presentation tokens](docs/usage/customizing.md#presentation-tokens): how much a fill, an edge, or a hover gives.
|
|
3303
|
+
- [Customizing → Material tokens](docs/usage/customizing.md#material-tokens): what an aesthetic sets, which you can set yourself, such as `--ui-radius`.
|
|
3304
|
+
- [Customizing → Aesthetic tokens](docs/usage/customizing.md#aesthetic-tokens): the knobs of each aesthetic, such as `--neo-offset`.
|
|
3305
|
+
|
|
3306
|
+
Use tokens when the change is a value, such as a color or a corner, rather than a choice between classes.
|
|
3307
|
+
|
|
3308
|
+
## Your own component classes
|
|
3309
|
+
|
|
3310
|
+
With the Tailwind source entrypoints, every component, helper, and axis class is a Tailwind utility, so one class of yours can carry the whole recipe:
|
|
3311
|
+
|
|
3312
|
+
```css
|
|
3313
|
+
@import "@codenhub/styles/tw";
|
|
3314
|
+
|
|
3315
|
+
@utility btn-save {
|
|
3316
|
+
@apply btn primary soft sm;
|
|
3317
|
+
}
|
|
3318
|
+
```
|
|
3319
|
+
|
|
3320
|
+
```html
|
|
3321
|
+
<button class="btn-save" type="submit">Save changes</button>
|
|
3322
|
+
```
|
|
3323
|
+
|
|
3324
|
+
`btn-save` behaves like `btn primary soft sm` written out, aesthetics and hover included, and changing the recipe changes every save button. Modifiers that belong to one component, such as `.tight` on `.stack` or `.icon` on `.btn`, are utilities too, so `@apply btn icon` gives you an icon button of your own. [Tailwind CSS v4 → Build your own components with `@apply`](docs/integrating/tailwind.md#build-your-own-components-with-apply) has the rules, including why `@utility` beats a plain rule.
|
|
3325
|
+
|
|
3326
|
+
With the compiled CSS there is no `@apply`. Keep the recipe in one place on the other side instead: a template partial, a component in your framework, or a constant such as `const saveButton = "btn primary soft sm"`.
|
|
3327
|
+
|
|
3328
|
+
Use your own class when the same combination names one thing in your design, such as the save button or the plan card.
|
|
3329
|
+
|
|
3330
|
+
## Next
|
|
3331
|
+
|
|
2894
3332
|
[Accessibility](docs/accessibility.md) lists what the CSS does for your users and what your application still owes them.
|
|
2895
3333
|
|
|
2896
3334
|
<!-- Source: docs/accessibility.md -->
|
|
@@ -2905,14 +3343,14 @@ This page lists both halves: what the CSS provides, and what your application ha
|
|
|
2905
3343
|
|
|
2906
3344
|
### Focus and state
|
|
2907
3345
|
|
|
2908
|
-
| Feature | Behavior
|
|
2909
|
-
| ------------------ |
|
|
2910
|
-
| `:focus-visible` | A focus outline on every element, drawn with `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`.
|
|
2911
|
-
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the focus outline and change their border to the intent color. Typed text in a field with an intent class is in the intent's readable tone, `--intent-strong`.
|
|
2912
|
-
| Invalid controls | `aria-invalid="true"` on a form control turns its border, focus color, and typed text destructive, the text in the destructive readable tone.
|
|
2913
|
-
| Disabled controls | `disabled`, `aria-disabled="true"`, `data-disabled`, and `.disabled` show the disabled cursor and opacity, and a disabled control does not react to hover, including inside an `.input-group`.
|
|
2914
|
-
| Open state | `data-state="open"` on a `.tooltip` shows its bubble without hover or focus, and `data-state="closed"` hides it while hovered or focused. No other component reads either.
|
|
2915
|
-
| Native `<dialog>` | A `<dialog>` with `.card`, `.panel`, or
|
|
3346
|
+
| Feature | Behavior |
|
|
3347
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
3348
|
+
| `:focus-visible` | A focus outline on every element, drawn with `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. On a component filled past half strength, such as a `.solid.primary` button, a 2px line of the page color runs inside the ring, so the ring stays visible on a fill of its own color. |
|
|
3349
|
+
| Form control focus | `.ipt`, `.textarea`, and `.select` draw the focus outline and change their border to the intent color. Typed text in a field with an intent class is in the intent's readable tone, `--intent-strong`. |
|
|
3350
|
+
| Invalid controls | `aria-invalid="true"` on a form control turns its border, focus color, and typed text destructive, the text in the destructive readable tone. A checkbox, radio, or switch is drawn exactly as its `.destructive` version, checked mark included. |
|
|
3351
|
+
| Disabled controls | `disabled`, `aria-disabled="true"`, `data-disabled`, and `.disabled` show the disabled cursor and opacity, and a disabled control does not react to hover, including inside an `.input-group`. |
|
|
3352
|
+
| Open state | `data-state="open"` on a `.tooltip` shows its bubble without hover or focus, and `data-state="closed"` hides it while hovered or focused. No other component reads either. |
|
|
3353
|
+
| Native `<dialog>` | A `<dialog>` with `.card`, `.panel`, `.surface`, or your own class that does `@apply surface` stays hidden until it is opened, and opens centered. A margin you set on the dialog, as a utility or in your own CSS, still overrides the centering, except that a margin utility does not when your class applies `surface` in plain CSS; see [Customizing → Composition helpers](docs/usage/customizing.md#composition-helpers). With the [classless stylesheet](docs/setup.md#classless-html), a plain `<dialog>` is a `.card.floating` and behaves the same; with the other entrypoints, a dialog with none of these classes is left alone. Trapping focus, closing on Escape, and making the page behind it inert are up to your application. |
|
|
2916
3354
|
|
|
2917
3355
|
### Visually hidden content
|
|
2918
3356
|
|
|
@@ -2924,7 +3362,7 @@ WCAG 1.4.11 asks that the boundary of a control you need to see to use it measur
|
|
|
2924
3362
|
|
|
2925
3363
|
| Feature | Behavior |
|
|
2926
3364
|
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2927
|
-
| Text controls at rest | A text control's border rests lighter than its intent's full border, below 3:1 by choice, so hover and focus have a stronger tone to change to.
|
|
3365
|
+
| Text controls at rest | A text control's border rests lighter than its intent's full border, below 3:1 by choice, so hover and focus have a stronger tone to change to. [Increased contrast](#increased-contrast) draws it whole. |
|
|
2928
3366
|
| Toggles | `.checkbox`, `.radio`, and `.switch` draw their full border, because the border is the only thing that shows an unchecked box. A `.switch` with its own `.edgeless` is the exception, below. |
|
|
2929
3367
|
| `.edgeless` from a container | Ignored by every text control and toggle: a container cannot remove the border of a field or toggle inside it. |
|
|
2930
3368
|
| `.edgeless` on the control | `.ipt`, `.textarea`, `.select`, and `.switch` drop their border and no longer meet 1.4.11 at rest. `.switch.ghost` keeps its border even then, because with no fill and no border nothing would show the track. |
|
|
@@ -2949,11 +3387,11 @@ With no intent class, the outline is the quiet border gray, `--color-border`. To
|
|
|
2949
3387
|
|
|
2950
3388
|
### Motion and transparency
|
|
2951
3389
|
|
|
2952
|
-
| Feature | Behavior
|
|
2953
|
-
| -------------------------- |
|
|
2954
|
-
| Reduced motion, components | With `prefers-reduced-motion: reduce`, every component stops its own transitions and animations: the skeleton and progress shimmer, the indeterminate bar, the tooltip, the toggle marks, and the press. |
|
|
2955
|
-
| Reduced motion, whole page | The reset also shortens every other animation and transition on the page.
|
|
2956
|
-
| Reduced transparency | With `prefers-reduced-transparency: reduce`, `.glass
|
|
3390
|
+
| Feature | Behavior |
|
|
3391
|
+
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3392
|
+
| Reduced motion, components | With `prefers-reduced-motion: reduce`, every component stops its own transitions and animations: the skeleton and progress shimmer, the indeterminate bar, the tooltip, the toggle marks, and the press's motion. The press itself still happens, at once, on the default look and under every aesthetic and solo class, since it is the feedback that a click landed. |
|
|
3393
|
+
| Reduced motion, whole page | The reset also shortens every other animation and transition on the page. |
|
|
3394
|
+
| Reduced transparency | With `prefers-reduced-transparency: reduce`, and in forced colors, `.glass`, `.glass-liquid`, and `.glass-solo` drop their blur and lens and become opaque. |
|
|
2957
3395
|
|
|
2958
3396
|
Each component's reduced-motion rule ships with the component, so every entrypoint that includes it respects reduced motion without the reset, including `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and each `/tw/*` component entrypoint. The complete and native entrypoints add the reset's page-wide rule.
|
|
2959
3397
|
|
|
@@ -2975,30 +3413,49 @@ The reset's rule is `!important` inside the `base` layer, and a layered `!import
|
|
|
2975
3413
|
|
|
2976
3414
|
Respecting reduced motion is not the whole requirement. Motion that keeps going next to content, such as a shimmer left on a card after it has loaded, needs a way to stop it for every user; see [Feedback → Animation that keeps running](docs/usage/feedback.md#animation-that-keeps-running).
|
|
2977
3415
|
|
|
3416
|
+
### Increased contrast
|
|
3417
|
+
|
|
3418
|
+
With `prefers-contrast: more`, the borders the package draws below 3:1 by choice are drawn at 3:1 or more against what is next to them, in both themes. Text, solid fills, and focus rings already meet their ratios and do not change. `prefers-contrast: less` is not read.
|
|
3419
|
+
|
|
3420
|
+
| Feature | Behavior |
|
|
3421
|
+
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3422
|
+
| Text controls | `.ipt`, `.textarea`, `.select`, and `.input-group` rest at their full border, so hover and focus no longer change its tone. The focus ring is the same. |
|
|
3423
|
+
| Neutral borders | `--color-border` takes a heavier gray, `neutral-600` in light and `neutral-400` in dark, so a card's border, a divider, a table's rules, and a neutral button's or badge's border meet 3:1. If you set `--color-border` yourself, your color is kept; measure it. |
|
|
3424
|
+
| Intent borders | An intent's border takes its strong tone, `--color-<intent>-strong`, so `.edged` on a `.soft` `.alert`, `.panel`, `.badge`, `.kbd`, `.code`, or `.pre` meets 3:1 against both its fill and the page, for every intent. |
|
|
3425
|
+
| Progress track | The track draws an outline in its border color, which meets 3:1 with or without an intent class. `.edgeless` on the bar or on a container still removes it. |
|
|
3426
|
+
| Glass | `.glass`, `.glass-liquid`, and `.glass-solo` drop their blur and lens and become opaque, as with reduced transparency, and their white hairline becomes a gray border. |
|
|
3427
|
+
| Chunky tile | `.chunky-tile` draws tiles and surfaces in the heavier gray its controls use. |
|
|
3428
|
+
|
|
3429
|
+
What you remove stays removed: `.edgeless` on a control still drops its border, and a `.panel` still has none unless you add `.edged`.
|
|
3430
|
+
|
|
2978
3431
|
### Color and contrast
|
|
2979
3432
|
|
|
2980
3433
|
The measurements below are for the shipped colors. Once you change a color token, measure your own pairs again; [Theming → Change the brand color](docs/usage/theming.md#change-the-brand-color) shows which pairs to check.
|
|
2981
3434
|
|
|
2982
|
-
| Feature | Behavior
|
|
2983
|
-
| ----------------------- |
|
|
2984
|
-
| Filled labels | Every intent's `.solid` label meets 4.5:1 (WCAG 1.4.3) as normal text, in both themes. The lowest is success, at 5.14:1.
|
|
2985
|
-
| Warning text | `--color-warning-contrast` is near-black, not the page color, because an amber dark enough to carry white text no longer looks amber.
|
|
2986
|
-
| Partial fills | The contrast text color appears only on fills above half strength, so a text control's capped fill keeps the intent's own text color.
|
|
2987
|
-
| Intent color in dark | In dark mode a `.soft` or `.ghost` label uses the `-300` shade of its intent, so it keeps its hue instead of turning white.
|
|
2988
|
-
|
|
|
2989
|
-
|
|
|
3435
|
+
| Feature | Behavior |
|
|
3436
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3437
|
+
| Filled labels | Every intent's `.solid` label meets 4.5:1 (WCAG 1.4.3) as normal text, in both themes. The lowest is success, at 5.14:1. |
|
|
3438
|
+
| Warning text | `--color-warning-contrast` is near-black, not the page color, because an amber dark enough to carry white text no longer looks amber. |
|
|
3439
|
+
| Partial fills | The contrast text color appears only on fills above half strength, so a text control's capped fill keeps the intent's own text color. |
|
|
3440
|
+
| Intent color in dark | In dark mode a `.soft` or `.ghost` label uses the `-300` shade of its intent, so it keeps its hue instead of turning white. |
|
|
3441
|
+
| Text on a filled parent | A neutral component that is not `.solid`, such as a badge in a `.btn.primary` or a ghost button in a `.card.solid.primary`, prints in its parent's text color instead of the page's, so it keeps the parent's contrast. |
|
|
3442
|
+
| Print | A page prints in the light theme, and filled components print their background (`print-color-adjust: exact`), so contrast text never lands on blank paper. A `dark:` utility still applies in print; see [Theming → Printing](docs/usage/theming.md#printing). |
|
|
3443
|
+
| Toggle marks | A checked mark measures at least 3:1 against every fill its presentation can give it, in both themes. |
|
|
3444
|
+
| Soft fills with borders | `.edged` on a `.soft` `.alert`, `.panel`, `.badge`, `.kbd`, `.code`, or `.pre` draws a border that measures below 3:1 against the 12% fill for some intents, which ones depending on the theme. Measure the pair before relying on that border as a control's boundary. [Increased contrast](#increased-contrast) brings every one of them to 3:1. |
|
|
2990
3445
|
|
|
2991
3446
|
### Forced colors and direction
|
|
2992
3447
|
|
|
2993
|
-
| Feature | Behavior
|
|
2994
|
-
| -------------- |
|
|
2995
|
-
| Forced colors | With `forced-colors: active`, borders, checked states,
|
|
2996
|
-
| Text direction | Under `dir="rtl"`, the quote bar, the alert icon, the progress fill, the vertical divider, the switch knob, table alignment, and a select's chevron all switch sides. No class is needed.
|
|
3448
|
+
| Feature | Behavior |
|
|
3449
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3450
|
+
| Forced colors | With `forced-colors: active`, borders, checked states, a `.select`'s chevron, and a progress bar's value stay visible, drawn in system colors, under every aesthetic. An aesthetic that draws its border as an inner shadow, such as `.pixel`, draws a real border instead. Glass becomes opaque, because forced colors keep a background's transparency. |
|
|
3451
|
+
| Text direction | Under `dir="rtl"`, the quote bar, the alert icon, the progress fill, the vertical divider, the switch knob, table alignment, and a select's chevron all switch sides. No class is needed. |
|
|
2997
3452
|
|
|
2998
|
-
In forced colors, `.text-control`, `.ipt`, `.textarea`, `.select`,
|
|
3453
|
+
In forced colors, `.text-control`, `.ipt`, `.textarea`, `.select`, the toggles, and your own class that does `@apply text-control` show a 2px `Highlight` focus outline. Inside an `.input-group`, the group draws the field's ring and the control inside draws none, so a focused field shows one ring. The native entrypoint gives unclassed text inputs, selects, textareas, checkboxes, and radios the same outline.
|
|
2999
3454
|
|
|
3000
3455
|
Checkboxes, radios, and switches use the system colors `Canvas`, `CanvasText`, `Highlight`, and `HighlightText`, so checked and unchecked stay distinct: a checked toggle is filled with `Highlight` and its mark is `HighlightText`, on the classes and in the native entrypoint alike. Shape, size, and spacing do not change.
|
|
3001
3456
|
|
|
3457
|
+
A `.progress` bar does the same: its filled part is `CanvasText` on a `Canvas` track with a `CanvasText` outline, whatever its intent or presentation, so a half-full bar does not read as an empty one. The `.active` shimmer is not drawn.
|
|
3458
|
+
|
|
3002
3459
|
## Required outside CSS
|
|
3003
3460
|
|
|
3004
3461
|
Use the HTML element that matches what the component does, and add the behavior the CSS cannot.
|
|
@@ -3039,6 +3496,110 @@ Use ARIA or data attributes where the element or your component library has no n
|
|
|
3039
3496
|
|
|
3040
3497
|
That completes the reading path. The [Introduction](docs/index.md) links every page, and [Composing](docs/usage/composing.md), [Customizing](docs/usage/customizing.md), and [Content and layout](docs/usage/content-and-layout.md) are the lookup pages for later.
|
|
3041
3498
|
|
|
3499
|
+
<!-- Source: docs/changelog/0.9.0.md -->
|
|
3500
|
+
|
|
3501
|
+
# 0.9.0
|
|
3502
|
+
|
|
3503
|
+
The build command is renamed `chs` and no longer runs Tailwind: it trims a stylesheet the package ships, so the package is the whole install. Twelve component variables, such as `--field-gap` and `--table-cell-padding`, are public and take effect when set on any ancestor. The release also fixes printing, bare native controls, disabled solo actions, forced colors, and sketch knobs, and adds the licence notice of the checkbox marks to every compiled file that carries them.
|
|
3504
|
+
|
|
3505
|
+
## Added
|
|
3506
|
+
|
|
3507
|
+
- **Twelve component variables.** `--tooltip-gap`, `--field-gap`, `--input-group-padding`, `--switch-height`, `--switch-width`, `--divider-color`, `--divider-width`, `--skeleton-base`, `--skeleton-sheen`, `--quote-width`, `--table-cell-padding`, and `--table-hover` join `--input-group-gap` and `--tooltip-linger` as public variables. Each one works on the component or on any element around it, so `.settings { --field-gap: 0.5rem; }` spaces every field in the region. `--table-hover` also works on a `tbody` or a row, for that part of the table only. See [Customizing → Component variables](docs/usage/customizing.md#component-variables).
|
|
3508
|
+
|
|
3509
|
+
## Changed
|
|
3510
|
+
|
|
3511
|
+
- **Breaking: the build command is `chs`.** `codenhub-styles build` is gone, with no alias; run `chs build` from a `package.json` script, or `npx @codenhub/styles build` from a shell. Do not type `npx chs`: an unrelated package on npm has that name, and npx would download and run it. See [Without a bundler → Build only the classes you use](docs/integrating/without-a-bundler.md#build-only-the-classes-you-use).
|
|
3512
|
+
- **Breaking: the command builds without Tailwind.** It reads the classes your files name and keeps their rules from a complete stylesheet the package ships in `dist/purge/`, so it needs Node.js 24 or newer and nothing else installed, and its output no longer changes with your Tailwind version. Three things it did through Tailwind are gone. It emits no Tailwind utilities, so `flex` or `mt-4` in your templates adds nothing. Variants are a fixed set of ten: `sm:`, `md:`, `lg:`, `xl:`, `2xl:`, `hover:`, `focus:`, `focus-visible:`, `active:`, and `dark:`. Variants do not stack, so `md:hover:primary` is not found. A project that needs any of those runs Tailwind and imports `/tw`, which still gives all three. See [Without a bundler → What it does not do](docs/integrating/without-a-bundler.md#what-it-does-not-do).
|
|
3513
|
+
- **Breaking: `@codenhub/styles` no longer has a peer dependency on `@tailwindcss/cli`.** Only the command used it. Remove it from your dependencies if nothing else uses it. `tailwindcss` stays an optional peer for `/tw`.
|
|
3514
|
+
- **`--input-group-gap` and `--tooltip-linger` work from a parent.** They took effect only when set on the `.input-group` or the `.tooltip` itself. A value set on an ancestor now reaches them.
|
|
3515
|
+
- **Sketch knobs reach every position.** `--sketch-radius`, `--sketch-radius-rounded`, `--sketch-radius-surface`, and `--sketch-radius-surface-rounded` set on a `.sketch` region, or on any element inside it, now shape every control and surface that rotates through the four outlines, such as `.btn`, `.ipt`, and `.card`. They skipped one element in four. A component that does not rotate, such as `.skeleton` or a bare control under `/native`, still takes only a knob set on the region. See [Aesthetics → Sketch](docs/usage/aesthetics.md#sketch).
|
|
3516
|
+
- **Small buttons and fields read the theme's line height.** `.btn.sm` and the small text fields took their leading from a variable private to Tailwind, which can change between Tailwind releases. They now read `--text-sm--line-height`. A `leading-*` utility on them still wins.
|
|
3517
|
+
- **Internal variables renamed.** The custom properties that switches, tooltips, quotes, tables, and elevation derive internally now start with `--_`, as the other internal ones do, such as `--_tooltip-top` and `--_table-rule`. They were documented as internal. CSS that set one of the old names, such as `--table-rule`, no longer does anything. Use a public variable or a utility instead.
|
|
3518
|
+
- **The homepage is the package's page on codenhub.dev.**
|
|
3519
|
+
|
|
3520
|
+
## Fixed
|
|
3521
|
+
|
|
3522
|
+
- **A tap leaves no hover tint.** On a touch screen a tapped button, interactive card, table row, checkbox, switch, select, or input group kept its hover color until something else was tapped. Hover now applies only where the pointer can hover, as Tailwind's `hover:` variant does.
|
|
3523
|
+
- **A bare `<progress>` takes the color it sits in.** Under `/native`, in a filled container, it drew the page's ink over the container's color.
|
|
3524
|
+
- **A bare quiet checkbox or radio keeps its checked plate.** Under `/native`, a checked `.ghost` or `.soft` checkbox or radio showed its resting plate, and a mixed one also kept the neutral 20% cap.
|
|
3525
|
+
- **A filled quotation prints with its plate.** A `.quote.solid` printed white text on white paper.
|
|
3526
|
+
- **A solid solo element prints with its plate.** The `-solo` classes left their fill off paper, as the quotation did.
|
|
3527
|
+
- **A solo action disabled by class or data attribute does not press.** The neobrutalism, chunky tile, and cyber solos guarded their press with `:disabled` and `aria-disabled` only. A link or `role="button"` marked `.disabled` or `data-disabled` still sank when clicked.
|
|
3528
|
+
- **`@apply progress` and `@apply select` keep their forced colors.** The applied bar's value was repainted as Canvas, and the applied drop-down drew no arrow.
|
|
3529
|
+
- **Compiled CSS carries the licence of the checkbox marks.** The marks come from Lucide (ISC, itself from Feather, MIT). Each compiled file that inlines them now carries their copyright and permission notices in a `/*!` comment after Tailwind's banner, and `chs build` keeps it in its output.
|
|
3530
|
+
|
|
3531
|
+
## Documentation
|
|
3532
|
+
|
|
3533
|
+
- [Customizing → Generated palette](docs/usage/customizing.md#generated-palette) says the palette's `-edge` values do not follow `prefers-contrast: more`.
|
|
3534
|
+
- [Tailwind CSS v4](docs/integrating/tailwind.md) warns against clearing the `--text-*`, `--font-*`, and `--spacing` themes, which the package reads.
|
|
3535
|
+
|
|
3536
|
+
<!-- Source: docs/changelog/0.8.0.md -->
|
|
3537
|
+
|
|
3538
|
+
# 0.8.0
|
|
3539
|
+
|
|
3540
|
+
On `/tw` every intent, presentation, elevation, corner, size, and modifier class is a Tailwind utility, so `@apply btn primary soft sm` and `md:lg` work, and a `/tw` build emits only the classes your pages use. A `codenhub-styles build` command writes that trimmed stylesheet for a project that does not run Tailwind, with a bundler or without one. Text fields take `.sm` and `.lg`. The compiled stylesheets are about half their size before compression. The release also fixes a round of bugs in applied helpers, input groups, focus rings, nested aesthetics, and forced colors.
|
|
3541
|
+
|
|
3542
|
+
## Added
|
|
3543
|
+
|
|
3544
|
+
- **Every class is a utility on `/tw`.** The intents, the presentation (`solid`, `soft`, `ghost`, `edged`, `edgeless`), the elevation (`flat`, `raised`, `floating`), the corner patterns, and the sizes (`sm`, `lg`) are Tailwind utilities, so one class of yours can carry a whole recipe, `@utility btn-save { @apply btn primary soft sm; }`, and variants such as `hover:primary`, `md:lg`, and `dark:solid` apply a class under a condition. Before, they were plain classes: `@apply` could not name them and those variants compiled to nothing. See [Tailwind CSS v4 → Build your own components with `@apply`](docs/integrating/tailwind.md#build-your-own-components-with-apply).
|
|
3545
|
+
- **Component modifiers are utilities too.** `tight`, `loose`, `vertical`, `horizontal`, `between`, `narrow`, `wide`, `p-xs`, `p-sm`, `p-lg`, `flush`, `icon`, `pill`, `active`, `indeterminate`, `ruled`, `ruleless`, `interactive`, `hoverable`, and `pressable` work in `@apply` and with variants: `@apply stack tight`, `@apply btn icon pill`, and `md:tight`. A modifier still changes only the components that read it; `.wide` or `.active` on an element of your own sets nothing visible. The disabled state is not a utility: it stays the `disabled`, `aria-disabled`, or `data-disabled` attribute, or the `.disabled` class in markup.
|
|
3546
|
+
- **A build command for projects without a bundler.** `npx codenhub-styles build --content "app/views/**/*.erb" --output public/stylekit.css` reads your templates and writes one minified stylesheet with only the classes they use, for plain HTML, Rails, Laravel, Django, any server-rendered app, or a bundler build without Tailwind. It needs Node.js 24 or newer and `@tailwindcss/cli` and `tailwindcss` 4.3.2 or newer, below 5, installed, and stops with a one-line message naming whatever is missing. `@tailwindcss/cli` joins `tailwindcss` as an optional peer dependency, so a project that does not run the command installs nothing new. See [Without a bundler → Build only the classes you use](docs/integrating/without-a-bundler.md#build-only-the-classes-you-use).
|
|
3547
|
+
- **Sizes for text fields.** `.ipt`, `.select`, `.textarea`, and `.input-group` take `.sm` and `.lg` with the button's heights, text sizes, and corner step, so a small field and a small button line up in a row. See [Forms → Sizes](docs/usage/forms.md#sizes).
|
|
3548
|
+
- **`<dialog>` and `<progress>` under `/native`.** A plain `<dialog>` is a `.card.floating`, hidden until opened and centered, and a plain `<progress>` is a `.progress` filled to its `value`, or the indeterminate sweep with no `value`. See [Setup → Classless HTML](docs/setup.md#classless-html).
|
|
3549
|
+
- **Pages print in the light theme.** Under `@media print` every theme resolves to its light values, a forced-dark region's included, and filled components print their background (`print-color-adjust: exact`), so a solid button's white label no longer prints on blank paper. A `dark:` utility still applies in print. See [Theming → Printing](docs/usage/theming.md#printing).
|
|
3550
|
+
- **Presets.** A new guide, [Presets](docs/usage/presets.md), compares the three ways to set a look once: axis classes on a container, theme and material tokens, and your own component classes built with `@apply`.
|
|
3551
|
+
- **Dropping Tailwind's palette.** [Tailwind CSS v4 → Drop Tailwind's palette](docs/integrating/tailwind.md#drop-tailwinds-palette) has the `@theme` block that clears the hues your design does not use, and names the six ramps the theme needs.
|
|
3552
|
+
- **Parcel and `postcss-import`.** [Integrating → Bundlers that need a nudge](docs/integrating/index.md#bundlers-that-need-a-nudge) shows the one change each needs to find the stylesheet.
|
|
3553
|
+
|
|
3554
|
+
## Changed
|
|
3555
|
+
|
|
3556
|
+
- **Breaking: the `/tw` source moved from `dist/tw/` to `tw/`.** Imports through the package's exports, such as `@import "@codenhub/styles/tw"`, are unchanged. A path that reaches into `node_modules/@codenhub/styles/dist/tw/` breaks; point it at `tw/`. `dist/` now holds only finished CSS, plus an empty `index.d.ts` for TypeScript, so Django's `ManifestStaticFilesStorage` no longer fails `collectstatic` on it.
|
|
3557
|
+
- **Breaking: a `/tw` build emits only the classes your files name.** The `/tw` copy no longer lists every package class for Tailwind to generate, so a page using one button compiles to about 28 KB minified where it compiled to the whole 416 KB stylesheet. The intent, presentation, elevation, corner, and size classes, and the modifiers, are now generated only when they are found too, where the intent, presentation, and elevation classes used to be emitted always. A class name built at runtime, such as `` `btn ${tone}` ``, or one that comes from a CMS, has no styling until you write it out where Tailwind scans or list it with `@source inline("btn primary destructive soft")`. See [Tailwind CSS v4 → Troubleshooting](docs/integrating/tailwind.md#troubleshooting). The aesthetic classes are still always emitted.
|
|
3558
|
+
- **Breaking: the class families moved to a sublayer of `utilities`.** The intent, presentation, edge, elevation, corner, and size classes sat in the `components` layer; they now sit in `utilities.families`, which loses to every other rule in `utilities`. Your utilities and unlayered CSS still beat them. A rule of yours in the `components` layer, or in a layer of your own declared before `utilities`, no longer does: `@layer components { .my-card { --ui-fill: 50%; } }` now loses to `.solid` on the same element. Move such a rule into a utility, into unlayered CSS, or into a layer declared after `utilities`. See [Setup → Cascade layers](docs/setup.md#cascade-layers).
|
|
3559
|
+
- **Your utility beats a modifier and a component's inner defaults.** A modifier or size used to be a 0-2-0 rule nested in its component, so it beat a utility on the same element. Now a modifier only raises a flag its component reads at the weight of its default, so `gap-2` on a `.stack.tight`, `px-8` on a `.btn.sm`, `text-sm` on a `.label`, and `p-0 text-center` on a `.data-table` cell all apply. If you wrote such a utility and relied on the modifier winning, remove the utility.
|
|
3560
|
+
- **`.interactive`, `.hoverable`, and `.pressable` work on any element `box` paints, not only `.card`.** A `.badge`, a `.panel`, or your own `@apply box` element takes the hover and the press, and `.interactive` and `.pressable` set the pointer cursor on any element that carries them. If your own markup already uses one of these class names for something else, the element now takes the pointer cursor and the press; rename your class. A disabled `.interactive` or `.pressable` card shows the not-allowed cursor now, where it kept the pointer.
|
|
3561
|
+
- **`.ruled` and `.ruleless` on a container reach every table inside it.** They reached only the `.data-table` carrying them; a `.ruleless` wrapper now drops the body rules of every table it holds.
|
|
3562
|
+
- **The compiled stylesheets are about half their size before compression.** Each composed rule repeated its declarations once for every file that referenced it; they are stated once now. The compiled files also drop the fallbacks Tailwind writes for browsers without `color-mix()` or `@property`, which no supported browser needs. Minified, the complete stylesheet is about 198 KB, down from 416 KB, `/native` about 295 KB from 582 KB, and `/components` about 159 KB from 324 KB. With gzip they are about 17 KB, down from 20 KB, 22 KB from 26 KB, and 13 KB from 15 KB.
|
|
3563
|
+
- **Under `.pixel`, cards, panels, and alerts take no cut.** A surface keeps the inset ring and square corners but no longer clips, because the clip cut everything inside it: a tooltip in a card was never painted, and a child's shadow and focus outline were trimmed. Buttons, fields, and code blocks keep their stepped corners. See [Aesthetics → Pixel](docs/usage/aesthetics.md#pixel).
|
|
3564
|
+
- **A neutral component on a filled parent prints in the parent's text color.** A neutral badge in a `.btn.primary` read at 1.11:1, and a `.ghost` Cancel button in a `.card.solid.primary` printed the page's text on the card's fill. A neutral component that is not `.solid`, a text field included, now takes its label and its line from the filled component around it. A component with an intent keeps its own color. See [Buttons → Choose a fill and an edge](docs/usage/buttons.md#choose-a-fill-and-an-edge).
|
|
3565
|
+
- **A filled component's focus ring carries a line of the page color inside it.** On a component filled past half, such as `.solid.primary`, the outline sat over a fill of its own color and part of it vanished. A 2px line of `--color-background` now runs inside the ring, as it already did under `.pixel`; a focused `.input-group` takes the same line.
|
|
3566
|
+
- **A container's tracking no longer reaches its buttons and badges.** `.btn` and `.badge` fall back to `normal` letter spacing, and text fields set `normal`, so body copy's tracking under `/native` no longer widens every control. To track a region's buttons, set `--ui-label-tracking` on the container.
|
|
3567
|
+
- **`/native` sets body text on `body`, not on `p` and `li`.** Paragraphs and list items inherit their type from what they sit in, so a container that sets its own size, weight, or tracking applies it to the Markdown inside it; a paragraph in a bold 20px container is no longer 14px regular.
|
|
3568
|
+
- **Every press happens under reduced motion.** The default look, glass, cyber, sketch, and `.cyber-solo` dropped their press under `prefers-reduced-motion: reduce`, so a click gave no feedback. The press now lands at once, as the slab presses of neobrutalism and chunky tile already did.
|
|
3569
|
+
- **Glass and neobrutalism regions set the theme's font.** They set `font-family` from `--font-default` on the region, so a glass or neobrutalist region nested in pixel, cyber, sketch, or chunky tile no longer takes that aesthetic's face. A font set only with `font-family` on `body` or a container stops at these regions; set `--font-default` too, as [Theming → Fonts](docs/usage/theming.md#fonts) describes.
|
|
3570
|
+
- **`--focus-ring` is no longer declared at `:root`.** It resolved `--color-primary` there and inherited, so a region with its own `--color-primary` ringed its buttons in the page's color. Each focused element now falls back to its own primary at 64%, and a `--focus-ring` you set on an ancestor still inherits. `var(--focus-ring)` in your own CSS with nothing set now resolves to nothing; write the fallback, `var(--focus-ring, color-mix(in srgb, var(--color-primary) 64%, transparent))`.
|
|
3571
|
+
- **Eight neutral palette values changed.** `/palette`'s neutral soft `page` and `subtle` cells are now read from the components that draw them, `.alert` and `.kbd`, so they match what those components render; `--palette-neutral-soft-page-bg` is lighter in the light theme and darker in the dark one. Every other palette value is unchanged.
|
|
3572
|
+
- **The package declares its types.** Every stylesheet export leads with a `types` condition naming an empty declaration, and the top-level `types` field names `dist/index.d.ts`, so `import "@codenhub/styles";` typechecks under TypeScript 6's `noUncheckedSideEffectImports`, where it failed with TS2882. `tw/` holds its own `index.d.ts`.
|
|
3573
|
+
- **`prefers-contrast: more` is honored.** Nothing read it before. With it, text fields rest at their full border, neutral borders take a heavier gray, an intent's border takes its strong tone, a progress track is outlined, and glass turns opaque with a gray border, so each of those lines measures at least 3:1 in both themes. Readers who have not asked for more contrast see no change. [Accessibility → Increased contrast](docs/accessibility.md#increased-contrast) lists each one.
|
|
3574
|
+
|
|
3575
|
+
## Removed
|
|
3576
|
+
|
|
3577
|
+
- **`--surface-padding`.** The variable `.card` and `.panel` read their padding from, and that `.p-xs`, `.p-sm`, `.p-lg`, and `.flush` wrote, is gone; the padding modifiers now set the padding through their own flags. It was documented as internal wiring, but CSS that set `--surface-padding` on a card to change its padding no longer does anything; use a padding modifier, or set `padding` in your own unlayered CSS.
|
|
3578
|
+
|
|
3579
|
+
## Fixed
|
|
3580
|
+
|
|
3581
|
+
- **`@apply text-control` and `@apply surface` behave like the classes.** An applied field missed the rules that named `.text-control` from outside it: it drew the faint surface line instead of the 3:1 control line, took the intent of any container around it, ignored the `.soft`, `.solid`, and `.edgeless` limits and `aria-invalid`, and kept the plain focus ring in forced colors. An applied surface missed the neutral reset, so inside a `.card.success` it drew the card's green line, and a `<dialog>` with your own `@apply surface` class rendered while closed and pinned to the top-left once open. All of that now travels with the utility. See [Customizing → Composition helpers](docs/usage/customizing.md#composition-helpers).
|
|
3582
|
+
- **`@apply progress` draws its border.** An applied progress bar read an undefined border color and drew no track edge.
|
|
3583
|
+
- **Liquid glass layers reach your own surfaces.** A `@apply surface` element in a `.glass-liquid` region took the backdrop but no lens or rim.
|
|
3584
|
+
- **A neutral edged ghost button or badge under glass shows its edge.** Glass's white hairline needs a plate to be seen on, and on a light page it measured 1.01:1; an unfilled neutral button or badge draws glass's control line instead.
|
|
3585
|
+
- **An input group is the height of the controls beside it.** The control inside kept its own height within the group's border, so a group was 42px beside 40px fields and buttons.
|
|
3586
|
+
- **An input group draws one focus ring.** A focused field drew its own Highlight ring inside the group's in forced colors, and a focused button drew its ring inside the group's; the button keeps its ring and the group drops its own.
|
|
3587
|
+
- **A disabled control in an input group dims once.** The group and the control both dimmed, so the value showed at about a third of its opacity.
|
|
3588
|
+
- **A `.success` or invalid input group prints its typed text in that intent.** The control inside reset to neutral text while the group drew its line in the intent.
|
|
3589
|
+
- **An invalid checkbox is drawn as a destructive one.** A checked invalid checkbox in the dark theme drew the neutral near-black mark on red, at 3.28:1.
|
|
3590
|
+
- **Every intent hint is readable.** `.hint.destructive` printed a fill color at 3.28:1 on the dark page, and the other intents were not wired; each built-in intent now prints its strong tone, 4.5:1 or better in both themes.
|
|
3591
|
+
- **A themed region re-resolves its text color.** `<aside class="dark">` on a light page kept the near-black text it inherited and printed it on its dark ground; the theme selectors now set `color` too.
|
|
3592
|
+
- **A progress bar shows its value in forced colors.** The value was repainted as Canvas on a Canvas track, so a half-full bar read as empty.
|
|
3593
|
+
- **Glass is opaque in forced colors.** `.glass`, `.glass-liquid`, and `.glass-solo` stayed translucent and showed the page through the pane; they now take the opaque ground of the reduced-transparency fallback.
|
|
3594
|
+
- **A container's fill reaches a card, panel, or alert.** A `.card` inside a `.soft` container drew the grey slab of a `.card.soft`, and a container's `.ghost` or `.solid` landed a panel or alert on its quiet plate; they now draw the fill they resolve.
|
|
3595
|
+
- **A box with no intent draws in the aesthetic's ink.** A bare `box` under `.neobrutalism` drew the theme's grey line and slab beside a `.neutral` sibling's black ink.
|
|
3596
|
+
- **A `.raised` button keeps its shadow while pressed.** With no aesthetic it dropped from `0 1px 3px` to `0 0 3px` while held down.
|
|
3597
|
+
- **Code in a native `pre` draws no chip.** `pre code` cleared only the chip's fill and padding, so `.cyber` drew a glowing box inside the block, neobrutalism's border indented the first line, and `.pixel` drew an inset ring.
|
|
3598
|
+
- **A solo element keeps its own transition and transform.** A solo class replaced an element's `transition` list, so a toast stopped animating its fade and height; only actions take the press's transition now, and the press moves through `translate` or `scale`, leaving `transform` alone.
|
|
3599
|
+
- **Nested aesthetics take the nearest region's look.** A liquid region inside another aesthetic nested in a liquid one lost its layers, and a sketch region in the same position lost its rotation and dashed accents.
|
|
3600
|
+
- **`.alert.neobrutalism` sits on the slab.** An alert carrying the class itself rested flat, unlike one inside a `.neobrutalism` region and unlike `.card.neobrutalism`.
|
|
3601
|
+
- **Native elements under `.sketch` take the dashed accents.** Under `/native`, a plain `<hr>`, `<table>`, and checkbox draw the dashes `.divider`, `.data-table`, and `.checkbox` do.
|
|
3602
|
+
|
|
3042
3603
|
<!-- Source: docs/changelog/0.7.0.md -->
|
|
3043
3604
|
|
|
3044
3605
|
# 0.7.0
|