@codenhub/styles 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +7 -7
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -1
  4. package/dist/aesthetics/glass.css +1 -1
  5. package/dist/aesthetics/index.css +1 -1
  6. package/dist/aesthetics/neobrutalism.css +1 -1
  7. package/dist/aesthetics/pixel.css +1 -1
  8. package/dist/aesthetics/sketch.css +1 -0
  9. package/dist/components.css +1 -1
  10. package/dist/index.css +1 -1
  11. package/dist/native.css +1 -1
  12. package/dist/palette.css +1 -1
  13. package/dist/theme.css +1 -1
  14. package/dist/tw/aesthetics/chunky-tile.css +144 -128
  15. package/dist/tw/aesthetics/cyber.css +138 -96
  16. package/dist/tw/aesthetics/glass.css +332 -66
  17. package/dist/tw/aesthetics/index.css +3 -1
  18. package/dist/tw/aesthetics/neobrutalism.css +113 -54
  19. package/dist/tw/aesthetics/pixel.css +137 -87
  20. package/dist/tw/aesthetics/sketch.css +333 -0
  21. package/dist/tw/box.css +139 -52
  22. package/dist/tw/components/button.css +67 -28
  23. package/dist/tw/components/feedback.css +85 -67
  24. package/dist/tw/components/form.css +300 -230
  25. package/dist/tw/components/index.css +17 -9
  26. package/dist/tw/components/loader.css +38 -30
  27. package/dist/tw/components/surface.css +89 -129
  28. package/dist/tw/components/tooltip.css +91 -47
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +19 -28
  31. package/dist/tw/index.css +15 -19
  32. package/dist/tw/intent.css +47 -32
  33. package/dist/tw/native.css +58 -42
  34. package/dist/tw/palette.css +455 -446
  35. package/dist/tw/presentation.css +20 -29
  36. package/dist/tw/reset.css +21 -28
  37. package/dist/tw/surface.css +61 -0
  38. package/dist/tw/theme.css +156 -153
  39. package/dist/tw/utilities/content.css +283 -205
  40. package/dist/tw/utilities/layout.css +17 -27
  41. package/docs/accessibility.md +63 -46
  42. package/docs/changelog/0.5.0.md +38 -0
  43. package/docs/changelog/0.6.0.md +52 -0
  44. package/docs/changelog/index.md +2 -0
  45. package/docs/concepts.md +6 -6
  46. package/docs/index.md +12 -3
  47. package/docs/integrating/nextjs.md +1 -1
  48. package/docs/integrating/tailwind.md +14 -6
  49. package/docs/setup.md +28 -8
  50. package/docs/usage/aesthetics.md +88 -18
  51. package/docs/usage/buttons.md +14 -14
  52. package/docs/usage/composing.md +12 -12
  53. package/docs/usage/content-and-layout.md +25 -24
  54. package/docs/usage/customizing.md +92 -55
  55. package/docs/usage/feedback.md +5 -3
  56. package/docs/usage/forms.md +61 -45
  57. package/docs/usage/theming.md +41 -42
  58. package/docs/usage/tooltips.md +37 -17
  59. package/llms-full.txt +590 -304
  60. package/llms.txt +6 -0
  61. package/package.json +15 -5
@@ -1,5 +1,3 @@
1
- @reference "tailwindcss";
2
-
3
1
  @utility view {
4
2
  @apply flex gap-(--layout-gap);
5
3
 
@@ -78,10 +76,10 @@
78
76
  }
79
77
  }
80
78
 
81
- /* An indicator: intent only, and no presentation token at all. A rule is a line,
82
- and neither a fill class nor an edge class has anything to say about one -- while
83
- reading both used to mean a container's `.ghost.edgeless` made every `<hr>`
84
- inside it invisible. The width is the aesthetic's line width, unscaled. */
79
+ /* An indicator: intent only, and no presentation token at all. A rule is a
80
+ line, and neither a fill class nor an edge class has anything to say about
81
+ one -- a container's `.ghost.edgeless` must not make every `<hr>` inside it
82
+ invisible. The width is the aesthetic's line width, unscaled. */
85
83
  @utility divider {
86
84
  --divider-color: var(--intent-color);
87
85
  --divider-width: var(--ui-border-width, var(--border-width));
@@ -90,11 +88,13 @@
90
88
 
91
89
  border-color: var(--divider-color);
92
90
  border-top-width: var(--divider-width);
91
+ border-top-style: var(--ui-rule-style, var(--ui-line-style, solid));
93
92
 
94
93
  &.vertical {
95
94
  @apply h-auto w-0 self-stretch border-s border-t-0;
96
95
 
97
96
  border-inline-start-width: var(--divider-width);
97
+ border-inline-start-style: var(--ui-rule-style, var(--ui-line-style, solid));
98
98
  }
99
99
  }
100
100
 
@@ -106,27 +106,17 @@
106
106
  }
107
107
  }
108
108
 
109
- /* The gap this closes: `sr-only` is Tailwind's own name and the one a consumer
110
- reaches for out of habit, but on the vanilla, ready-to-import entries (`.`,
111
- `/native`) there is no Tailwind utility layer to reach -- those compile real
112
- CSS from a scan of `src/` alone, which used the class nowhere, so `sr-only`
113
- silently did nothing there. It worked fine on a `/tw` entry, where the
114
- consumer's own Tailwind build supplies it, and that split made the gap easy
115
- to miss: it read as a package decision rather than a hole in one build and
116
- not the other.
117
-
118
- Not published as `.sr-only` itself. `.table` already did that once --
119
- shadowing Tailwind's own `display: table` utility until a consumer needed
120
- the one this package ate -- and `registry.test.ts`'s collision check exists
121
- because of it (see `data-table`'s `renamedFrom` in `registry.json`). Naming
122
- this `sr-only` would trip the same trap from the other direction: on a `/tw`
123
- entry a real Tailwind build runs too, and two same-named utilities in the
124
- same cascade is exactly the silent collision that test polices.
125
- `.visually-hidden` says the same thing under a name nothing else claims,
126
- using the standard clip-based recipe. `@source inline("visually-hidden")` in
127
- `utilities/index.css` force-emits it on the entries with no scannable markup
128
- of their own, the same mechanism that file's inline list already uses for
129
- every layout and content utility name in it. */
109
+ /* `.visually-hidden`, the standard clip-based recipe. Tailwind's `sr-only` does
110
+ nothing on the vanilla entries (`.`, `/native`), which compile real CSS from
111
+ a scan of `src/` alone with no Tailwind utility layer to reach, so the
112
+ package publishes its own.
113
+
114
+ It is not named `.sr-only`: on a `/tw` entry the consumer's own Tailwind
115
+ build supplies that utility, and two same-named utilities in one cascade
116
+ collide silently -- the collision `registry.test.ts` polices (see
117
+ `data-table`'s `renamedFrom` in `registry.json`). `@source
118
+ inline("visually-hidden")` in `utilities/index.css` force-emits it on the
119
+ entries with no scannable markup of their own. */
130
120
  @utility visually-hidden {
131
121
  position: absolute;
132
122
  width: 1px;
@@ -11,14 +11,14 @@ This package provides CSS hooks for accessible states. It does not provide seman
11
11
 
12
12
  ### Focus and state
13
13
 
14
- | Feature | Behavior |
15
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
17
- | Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
18
- | Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
19
- | Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
20
- | Open state | `[data-state="open"]` styles supported surfaces and tooltips. |
21
- | Native `<dialog>` | `dialog:not([open])` and a centered `dialog[open]` are restored in author origin, so `.card` composed onto a `<dialog>` (the closest primitive the package ships) does not render open on load or lose its centering to another rule. Focus trapping, escape handling, and inert-background behavior stay outside this package. |
14
+ | Feature | Behavior |
15
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | `:focus-visible` | Global focus-visible outline using `--focus-ring`, `--focus-ring-width`, and `--focus-ring-offset`. |
17
+ | Form control focus | `.ipt`, `.textarea`, and `.select` draw the global focus ring and move their line to the intent color. |
18
+ | Invalid controls | `[aria-invalid="true"]` applies destructive border/focus color on form controls. |
19
+ | Disabled controls | `[disabled]`, `[aria-disabled="true"]`, `[data-disabled]`, and `.disabled` apply disabled cursor/opacity where supported. |
20
+ | Open state | `[data-state="open"]` on `.tooltip` shows its bubble without hover or focus, and `[data-state="closed"]` hides it while hovered or focused. No other component reads either. |
21
+ | Native `<dialog>` | On a `<dialog>` carrying `.card`, `.panel`, or `.surface`, the closed state and a centered open box are restored in author origin, so the dialog does not render open on load or lose its centering to another rule. It ships with those classes, on every entrypoint that has them, and leaves other dialogs alone. Focus trapping, escape handling, and inert-background behavior stay outside this package. |
22
22
 
23
23
  ### Visually hidden content
24
24
 
@@ -26,42 +26,59 @@ This package provides CSS hooks for accessible states. It does not provide seman
26
26
 
27
27
  ### Control boundaries
28
28
 
29
- | Feature | Behavior |
30
- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
- | Control boundaries | Text controls rest at `--_line-rest` of their intent, under 3:1 by choice; hover and focus take the tone whole. |
32
- | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole, because it is the only thing marking an unchecked box. |
33
- | Cascaded `.edgeless` | Floored on every text control: a container cannot erase the line of a field or toggle nobody classed. |
34
- | Own `.edgeless` | On the element, `.ipt`, `.textarea`, `.select`, and `.switch` do drop their line and do not meet 1.4.11 at rest, except `.switch.ghost`: `.ghost` already asks for zero fill, so dropping the line too would leave no boundary at all, and `.switch.ghost.edgeless` keeps it up instead. |
35
- | `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
36
- | Progress track | Known gap. `.progress`'s registered default, `.soft.edgeless`, does not meet 1.4.11 at rest -- measured ~1.1-1.3:1 against the page in both themes and across intents, down from the ~3.6-4.3:1 the prior fixed-neutral track cleared. An `.edged` presentation restores a boundary through the line for most intents (`primary.soft.edged` ~15:1), but not every one clears 3:1 there either (`warning.soft.edged` ~2.7:1). A consumer whose track must meet 1.4.11 should choose an `.edged` presentation and verify the intents they use, until this is resolved. |
29
+ | Feature | Behavior |
30
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
+ | Control boundaries | Text controls rest at a fraction of their intent's line, under 3:1 by choice, so hover and focus have a whole tone to move the line to. |
32
+ | Toggle boundaries | `.checkbox`, `.radio`, and `.switch` draw their line whole unless a `.switch` carries its own `.edgeless` (below), because the line is the only thing marking an unchecked box. |
33
+ | Cascaded `.edgeless` | Floored on every text control and toggle: a container cannot erase the line of a field or toggle nobody classed. |
34
+ | Own `.edgeless` | On the element, `.ipt`, `.textarea`, `.select`, and `.switch` do drop their line and do not meet 1.4.11 at rest, except `.switch.ghost`: `.ghost` already asks for zero fill, so dropping the line too would leave no boundary at all, and `.switch.ghost.edgeless` keeps it up instead. |
35
+ | `.checkbox`/`.radio` | Never drop their line, on the element or from a container. `.edgeless` on them is unsupported, not merely discouraged. |
36
+ | Progress track | By choice, the value carries the reading and the track is a quiet secondary cue. `.progress`'s registered default, `.soft.edgeless`, rests its track at ~1.1-1.3:1 against the page; the value clears 3:1 against that track for every intent but warning in light (~2.7:1) and info in dark (~2.9:1). `.ghost.edged` clears 3:1 for both the track's outline and the value, for every intent, in both themes -- choose it where the track itself must meet 1.4.11. |
37
37
 
38
38
  ### Motion and transparency
39
39
 
40
- | Feature | Behavior |
41
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
42
- | Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
43
- | Reduced-motion document | The reset additionally shortens document animations and transitions when `prefers-reduced-motion: reduce` matches. |
44
- | Reduced transparency | `.glass` drops its blur and becomes an opaque surface when `prefers-reduced-transparency: reduce` matches. |
45
-
46
- The loader fallback is embedded in loader CSS, so focused imports that include loaders honor reduced motion without the reset. This includes `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and `@codenhub/styles/tw/loader`. The complete and native entrypoints include both that fallback and the reset's broader animation and transition shortening.
40
+ | Feature | Behavior |
41
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
42
+ | Reduced-motion loaders | Loader variants use animated embedded SVGs normally and static masks when `prefers-reduced-motion: reduce` matches. |
43
+ | Reduced-motion components | Every component stops its own transitions and animations -- the skeleton and progress shimmer, the indeterminate sweep, the tooltip, toggle marks, and the press -- when `prefers-reduced-motion: reduce` matches. |
44
+ | Reduced-motion document | The reset additionally shortens every other animation and transition on the page when `prefers-reduced-motion: reduce` matches. |
45
+ | Reduced transparency | `.glass` and `.glass-liquid` drop their blur and lens and become an opaque surface when `prefers-reduced-transparency: reduce` matches. |
46
+
47
+ The loader fallback and each component's own reduced-motion rule ship with the component, so every entrypoint that includes it honors reduced motion without the reset -- `@codenhub/styles/components`, `@codenhub/styles/tw/components`, and each focused `/tw/*` component entrypoint included. The complete and native entrypoints add the reset's page-wide shortening on top.
48
+
49
+ The reset's rule is `!important` inside the `base` layer, and a layered `!important` beats an unlayered one, so your own `!important` cannot switch an animation back on. To keep one running for users who ask for reduced motion -- a progress indicator you consider essential, say -- declare it `!important` in a layer ordered before `base`. The order is set by the first `@layer` statement the browser meets, so yours goes before the package loads:
50
+
51
+ ```css
52
+ @layer essential-motion, theme, base, components, utilities;
53
+ @import "@codenhub/styles";
54
+
55
+ @layer essential-motion {
56
+ @media (prefers-reduced-motion: reduce) {
57
+ .upload-spinner {
58
+ animation-duration: 1s !important;
59
+ animation-iteration-count: infinite !important;
60
+ }
61
+ }
62
+ }
63
+ ```
47
64
 
48
65
  ### Color and contrast
49
66
 
50
- | Feature | Behavior |
51
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
- | Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1. |
53
- | Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
54
- | Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
55
- | Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white. |
56
- | Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
57
- | Soft-edged border | `.alert`, `.panel`, `.badge`, `.kbd`, and `.code`/`.pre`'s `.edged` border blends toward the component's own fill; at `.soft`'s 12% fill this misses 3:1 against the fill for some intents, and which ones misses depends on the theme. Known, not fixed -- `--color-border`/`--intent-border` is a shared blend seam every one of them reads, so a targeted fix would need to touch it for one component without moving it for the rest. |
67
+ | Feature | Behavior |
68
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
69
+ | Filled labels | Every intent's `.solid` label meets 1.4.3 (4.5:1) as normal text, in both themes. Lowest is success at 5.14:1. |
70
+ | Warning ink | `--color-warning-contrast` is the near-black tone, not the page tone. Amber dark enough to carry white is not amber. |
71
+ | Partial fills | Contrast ink appears only past a half fill, so a capped plate keeps `--intent-strong` rather than walking toward the page. |
72
+ | Intent hue in dark | `--intent-strong` is the `-300` shade in dark, so a soft or ghost label keeps its own hue instead of reading white. |
73
+ | Toggle marks | A checked mark meets 3:1 as a state indicator on every plate its fill class can paint, in both themes. |
74
+ | Soft-edged border | `.alert`, `.panel`, `.badge`, `.kbd`, and `.code`/`.pre`'s `.edged` border fades out by the component's fill amount; at `.soft`'s 12% fill this misses 3:1 against the fill for some intents, and which ones misses depends on the theme. Known, not fixed -- `--color-border`/`--intent-border` is a shared blend seam every one of them reads, so a targeted fix would need to touch it for one component without moving it for the rest. |
58
75
 
59
76
  ### Forced colors and direction
60
77
 
61
- | Feature | Behavior |
62
- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
63
- | Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
64
- | Text direction | Quote bar, alert icon, progress fill, vertical divider, and switch knob mirror under `dir="rtl"`. No class needed. |
78
+ | Feature | Behavior |
79
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
80
+ | Forced colors | `forced-colors: active` preserves visible borders and checked states using system colors. |
81
+ | Text direction | Quote bar, alert icon, progress fill, vertical divider, switch knob, table alignment, and a select's chevron mirror under `dir="rtl"`. No class needed. |
65
82
 
66
83
  In forced colors, class-based form controls including `.text-control` receive a 2px `Highlight` system-color focus outline. The native entrypoint provides the same visible system outline for unclassed text inputs, selects, textareas, checkboxes, and radios.
67
84
 
@@ -71,16 +88,16 @@ Custom checkboxes and radios use the system `Canvas`, `CanvasText`, `Highlight`,
71
88
 
72
89
  Use semantic HTML and behavior appropriate for the component.
73
90
 
74
- | UI | Required outside CSS |
75
- | ----------------- | --------------------------------------------------------------------------------------------------------------- |
76
- | Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`. |
77
- | Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. |
78
- | Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
79
- | Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
80
- | Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. |
81
- | Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
82
- | Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
83
- | Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
91
+ | UI | Required outside CSS |
92
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | Buttons | Use `<button>` for actions or accessible links for navigation. Add accessible names for `.btn.icon`. |
94
+ | Forms | Use labels, `type`, validation logic, `aria-describedby`, and error message relationships. Keep hints and errors outside the `<label>`, and give a `.switch` `role="switch"`. |
95
+ | Alerts | Add `role="status"` or `role="alert"` based on announcement urgency. |
96
+ | Toasts | Add live-region behavior, dismissal behavior, focus rules, and pause/timeout logic when needed. |
97
+ | Tooltips | Give the bubble a real `id` and `role="tooltip"`, and reference it from the trigger's `aria-describedby`. Dismiss it on Escape by writing `data-state="closed"` (see [Tooltips](./usage/tooltips.md)); the bubble is already hoverable. |
98
+ | Progress | Use semantic progress elements or ARIA values when numeric progress must be announced. |
99
+ | Skeletons/loaders | Mark decorative loading visuals with `aria-hidden="true"` and expose loading state elsewhere when needed. |
100
+ | Popovers/modals | Provide focus trapping, escape handling, inert background behavior, labels, and roles outside this CSS package. |
84
101
 
85
102
  ## State attribute guidance
86
103
 
@@ -95,7 +112,7 @@ Use ARIA or data attributes when native attributes are not available for the ele
95
112
  ```html
96
113
  <a class="btn secondary" aria-disabled="true">Unavailable</a>
97
114
  <span class="tooltip" data-state="open">
98
- <span class="tooltip-icon" tabindex="0" aria-label="More details" aria-describedby="more-details-bubble">?</span>
115
+ <button type="button" class="tooltip-icon" aria-label="More details" aria-describedby="more-details-bubble">?</button>
99
116
  <span class="tooltip-bubble" role="tooltip" id="more-details-bubble">More details</span>
100
117
  </span>
101
118
  ```
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: 0.5.0
3
+ ---
4
+
5
+ # 0.5.0
6
+
7
+ A foundation update with an opt-in sketch aesthetic and rounded sketch variant.
8
+
9
+ ## Breaking
10
+
11
+ - **The package sits in named cascade layers.** Every entrypoint declares Tailwind's layer order first and places everything in it: foundation tokens and the palette in `theme`, resets in `base`, the intent, presentation, elevation, and aesthetic classes in `components`, and every component in `utilities` -- including on `/components` and `/native`, which emitted them unlayered. Your own utility or unlayered CSS now beats all four class families on the same element, and an aesthetic on `<html>` keeps its tokens over the theme's in any load order. CSS that relied on a package class beating it now loses to it, and a rule of yours in `@layer components` contests the class families by specificity instead of losing outright. See [Setup](../setup.md).
12
+ - **Aesthetics set `--ui-corner`, not `--ui-radius`.** The corner is now a length at the default size that scales with the element (below); `--ui-radius` and `--ui-radius-surface` take a complete radius used as written, and every shipped aesthetic sets `--ui-corner` and `--ui-corner-surface` instead. If you set `--ui-radius` yourself, nothing changes for you. An aesthetic of your own moves to `--ui-corner` to get the scale and the corner pattern. `--ui-radius-pill` and `--ui-radius-tight` fall back through `--ui-radius`, then `--ui-corner`.
13
+ - **`--ui-button-weight` and `--ui-button-tracking` are `--ui-label-weight` and `--ui-label-tracking`**, read by `.badge` as well as `.btn`. The old names are removed without an alias. Under `.chunky-tile`, badges now take the heavier label too.
14
+ - **Explicit chip radii are no longer capped.** Checkbox, `.kbd`, and `.code` keep a small default radius when `--ui-radius-tight` is unset, but a supplied value now applies as a complete `border-radius`, including elliptical shapes. A consumer setting a large value now gets the large corner it asked for instead of the previous `--radius-small` ceiling.
15
+ - **Alias classes removed**, each a second name for a class that already had one: `.dense`, `.compact`, and `.spacious` (use `.p-xs`, `.p-sm`, and `.p-lg`); `.danger` and `.error` (use `.destructive`, and `.hint.destructive` for `.hint.error`); and `.elevation-none`, `.elevation-sm`, and `.elevation-md` (use `.flat`, `.raised`, and `.floating`). `.theme-light` and `.theme-dark` stay.
16
+ - **`.cyber` glows everything, flat or not.** Its glow moved from depth onto the new halo, so fields, chips, badges, and alerts glow too, `.flat` no longer puts the glow out, and `.raised` and `.floating` add no shadow under it.
17
+
18
+ ## Added
19
+
20
+ - **`.sketch`, `.sketch-rounded`, and `.sketch-solo`**: a hand-drawn treatment with uneven elliptical corners, a solid ink line (dashed checkboxes and rules under `.sketch`), a small offset shadow, and an optional consumer-supplied handwriting font. Sibling components rotate between four stable outlines; pill buttons take organic hand-drawn rounded pill contours without oval distortion; switch tracks, knobs, and chips take compact irregular box outlines, and radios stay subtly circular. The opt-in `.sketch-rounded` modifier class provides subtle, organic hand-drawn rounded contours across controls, surfaces, chips, and solo elements with 4-step sibling rotation and no egg-shaped distortion on wide elements. The solo class paints an unstyled element directly; the cascading class restyles package components. Both ship in the aesthetic aggregate and dedicated compiled and Tailwind CSS entrypoints. See [Aesthetics](../usage/aesthetics.md#sketch).
21
+ - **Corner scale.** `.p-xs` sets a size step of `0.5`, `.sm` and `.p-sm` `0.75`, and `.lg` and `.p-lg` `1.25`, and the corner is multiplied by it; when a size and a padding class sit on one element, the smaller step wins. A bevel stays a true 45 degrees, so `.cyber`'s 25% cap is gone. Depth follows the step down, never up: a `.p-xs` button sits on half its aesthetic's shadow, which also retires chunky tile's one selector list. `--ui-scale` overrides the step. A press that travels into the depth -- `--ui-active-translate-x` and `-y`, which chunky tile and neobrutalism now set instead of a translating `--ui-active-transform` -- scales the same way, elevation included, so it always lands flat on the slab, and a `.flat` button under either no longer drops. See [Customizing → Corner size and pattern](../usage/customizing.md#corner-size-and-pattern).
22
+ - **Corner pattern.** `.cut-diagonal` keeps the top-left and bottom-right corners and squares the other two, `.cut-diagonal-reverse` the opposite pair, wherever the corner is computed -- on a control under `.cyber`, but not a surface, which keeps its own shape. Both set `--ui-corner-tl`, `-tr`, `-br`, and `-bl`, which you can set yourself.
23
+ - **`--ui-line-style`**: `solid`, `dashed`, `dotted`, or `double` for every line a component draws -- the box's edge, the progress track, and dividers and table rules. `--ui-rule-style` overrides only dividers and table rules.
24
+ - **A halo**, `--ui-halo-blur`, `--ui-halo-spread`, and `--ui-halo-ink`: light in the element's own intent colour, which elevation does not scale and `.flat` does not remove.
25
+ - **`--ui-surface-image`**, a painted layer over a surface's plate -- cards, panels, alerts, and tooltips, not controls. Your own `background-image` on a surface still wins.
26
+ - **`--ui-label-case` and `--ui-label-shadow`**, beside the weight and tracking: the label's `text-transform`, and a `text-shadow` written without a colour so it takes the label's own.
27
+ - **`--ui-control-ink`**, a neutral line for controls read ahead of `--ui-ink`. Glass and chunky tile set it, so an unchecked checkbox under glass (1.33:1 before) and a field under chunky tile (1.65:1 before) draw a boundary that reads.
28
+
29
+ Under forced colours, each new part draws its plain version: a solid line, no halo, no painted layer, and no label shadow.
30
+
31
+ ## Fixed
32
+
33
+ - **A stylesheet survives being inlined.** The loader art's SVG data URIs carried a literal `</style>`, so a stylesheet pasted into an HTML `<style>` element ended at the first loader and silently dropped every rule after it. It is now percent-encoded.
34
+ - **Every entry ships the theme it promises.** `/components`, `/tw/components`, `/tw/feedback`, and `/tw/tooltip` shipped no colour token at all, and every other focused `/tw` component entry lost the palette ramp the tokens are built from. Each entry now declares every colour it reads, and `/components` works alone.
35
+ - **A line fades into a translucent plate instead of ringing it.** A neutral `.solid.edged`, and any plate `--ui-bg-alpha` thins, drew a ring 1.54:1 against its own fill; the line now fades toward transparent as the fill fills in. The generated palette's edge tokens follow.
36
+ - **A select's options read in the dark theme.** The open list is painted over the select's own background, and a ghost `.select` has none, so Chromium drew its default white popup under options that inherited the near-white text. Options and option groups now take the page's ground and text colour.
37
+ - **A region nested inside glass keeps its own surfaces.** Neobrutalism, pixel, chunky tile, and cyber left glass's translucent ground, fill alpha, and two-layer surface shadow in place, so their cards inside a glass region came out as half glass. Every aesthetic now clears them.
38
+ - **Surfaces under glass stay glass.** A neutral `.card.soft`, `.panel`, and `.alert` sat opaque inside a glass region, and `.card.soft.glass` did on the same element; their quiet ground is now a private default beneath `--ui-surface-ground`.
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: 0.6.0
3
+ date: 2026-09-29
4
+ ---
5
+
6
+ # 0.6.0
7
+
8
+ A liquid-glass modifier, glass that keeps every presentation's fill whole, hoverable tooltips, native field decorations with an `.unadorned` opt-out, indeterminate checkboxes and list-box selects, and a round of fixes to tables, controls, the focus ring, right-to-left layout, and reduced motion on every entrypoint.
9
+
10
+ ## Added
11
+
12
+ - **`.glass-liquid`**, a modifier of `.glass`: continuous corners, a bright rim, a lighter blur, a clearer ground, and a lens that bends what is behind a surface at its edges (Chromium today; Firefox and Safari keep the rim, blur, and ground). Put it beside `.glass` on the region, or on one surface inside a glass region; `.glass-solo.glass-liquid` paints the same pane on an element the package does not style. Without `.glass` in scope it changes the corners and nothing else. See [Aesthetics → Liquid glass](../usage/aesthetics.md#liquid-glass).
13
+ - **`.unadorned`** removes the browser's own field decorations -- the date picker button, number spin buttons, and search clear button and magnifier -- on a control or on its `.input-group`, for a field whose adornments you draw yourself. See [Forms → Icons](../usage/forms.md#icons).
14
+ - **`[data-state="closed"]` on `.tooltip`** hides the bubble while it is hovered or focused, for an Escape handler, and **`--tooltip-linger`** (300ms) sets how long a bubble stays after the pointer leaves its trigger. See [Tooltips](../usage/tooltips.md).
15
+ - **An indeterminate `.checkbox`** (and a native one under `/native`) draws the checked plate with a dash for its mark, and a Highlight plate in forced colors. See [Forms → Toggles](../usage/forms.md#toggles).
16
+ - **A multi-row `.select`** -- `multiple`, or a `size` above 1 -- draws as a list box: no chevron, as tall as its rows, with the chevron's room given back. It used to keep the chevron and a one-row height.
17
+ - **`--ui-rule-ink`**, the neutral line color of a table's frame and rules, read ahead of `--ui-ink`. Glass sets it so a table's lines read on its plate; every other aesthetic clears it.
18
+
19
+ ## Changed
20
+
21
+ - **Glass no longer thins fills.** It stopped setting `--ui-bg-alpha: 0.8`, so a `.solid` button, a checked toggle, and a `.solid` card are opaque under glass like everywhere else. Translucency comes from the surface ground alone. Glass buttons also take the base `scale(0.97)` press (it was `none`).
22
+ - **A neutral `.solid` rests on the page background.** Its capped 20% fill was painted over nothing and showed whatever was behind it; it is now an opaque grey. The generated `--palette-neutral-solid-bg` and `-bg-hover` values changed to match, in both themes.
23
+ - **Tables frost under glass and keep their lines.** A default or `.soft` `.data-table` blurs like a surface, its frame and rules read `--ui-rule-ink`, and a bare `<table>` under `/native` reads it too.
24
+ - **A table takes the surface corner and is never clipped.** The four corner cells round instead of an `overflow` clip, so a `<caption>` is untouched in every engine; a cell spanning rows into a bottom corner leaves that corner square. `.table-wrap` makes room for an aesthetic's halo (cyber's glow) inside its scroll box, so put the aesthetic class on the wrapper or an ancestor.
25
+ - **Table rules draw at the aesthetic's line width**, uncapped, so `.chunky-tile`'s 2px frame no longer sits over a 1px row rule and `.pixel` draws them at all.
26
+ - **The focus ring is pulled in by the focused element's line width.** `--focus-ring-offset` is no longer declared at `:root`; its default is computed on the element, so it lands on the edge under every aesthetic. Setting `--focus-ring-offset` yourself still wins, but reading `var(--focus-ring-offset)` in your own CSS now needs a fallback.
27
+ - **Ink aesthetics draw controls in the control border.** Under `.neobrutalism`, `.pixel`, `.cyber`, and `.sketch`, a resting neutral field or toggle uses `--color-control-border` instead of the near-primary ink. A bare native checkbox or radio under `/native` takes the control ink too, and a `.neutral` field under glass takes it like an unclassed one.
28
+ - **Text controls keep the browser's own decorations.** The date picker button, number spin buttons, and search clear button were hidden on every text control, which also removed what they do. They are back by default; add `.unadorned` to remove them.
29
+ - **Buttons and fields take the surrounding font on every entrypoint.** `<button>` under `/native`, and every `.btn` and text control under `/components`, used the user agent's smaller control font (13.33px), so the same label printed at three sizes across elements and entrypoints. They inherit the page's font now, as they already did on `.`.
30
+ - **A default `.btn` is `--control-height` tall**, 40px, the same as `.ipt` beside it; it was 42px, and 44px under a 2px aesthetic line. Its vertical padding gives back the line width.
31
+ - **`/tw` keeps Tailwind's `2xl` breakpoint.** It redefined `--breakpoint-2xl` as 90rem, which moved every `2xl:` utility in a consumer's own Tailwind build from 96rem. `--breakpoint-xs` (30rem) is still added. Nothing in the package used the override, so the compiled entrypoints are unchanged.
32
+ - **Components honour reduced motion and fix `<dialog>` on every entrypoint.** `/components` and the focused `/tw/*` entries carry no reset, so the skeleton and progress shimmer, the indeterminate sweep, and the tooltip, toggle, and press transitions kept moving under `prefers-reduced-motion: reduce`, and a `<dialog class="card">` rendered open on load. Each component now stops its own motion, and the `<dialog>` fix ships beside `.card`. The fix now reaches only a dialog carrying `.card`, `.panel`, or `.surface`, so a dialog the package does not style keeps its own margin.
33
+ - **A tooltip bubble is hoverable.** It lingers for `--tooltip-linger` after the pointer leaves the trigger and stays open while the pointer is over it, as WCAG 1.4.13 asks; it used to vanish on the way. A closed bubble is `visibility: hidden` rather than only transparent, so a bubble no `aria-describedby` points at is no longer read in place while closed.
34
+ - **`.alert-icon` sizes without `!important`.** It set its 1.25rem size `!important`, which no consumer rule could override. It sets `--ic-size` for `@codenhub/icons` glyphs and a plain size for any other icon now, so your own rule resizes it. An icon set that sizes itself in an unlayered rule under another knob keeps its own size.
35
+ - **The Tailwind CSS peer range is `>=4.3.2 <5`.** It claimed `>=4.0.0`, but the `/tw` source needs `@source not` and `@source inline()` (Tailwind 4.1) and is tested against 4.3.2. The peer is still optional, and only `/tw` needs it.
36
+ - **Browser floor is Firefox 128.** Non-inheriting elevation relies on `@property`; the documented floor was Firefox 121.
37
+
38
+ ## Fixed
39
+
40
+ - **A switch's knob sits inside `.pixel`'s inset ring** instead of covering it, so an unchecked and a checked switch no longer read as two halves.
41
+ - **The sketch solo shadow is cast in the depth colour** rather than full ink.
42
+ - **No stray `.contents` rule.** The compiled `.`, `/native`, and `/components` stylesheets shipped Tailwind's `.contents{display:contents}`, picked up from the license text; the build no longer scans the package's root files.
43
+ - **Links under `/native` keep the size of their text.** `a` was mapped to body copy, so a link inside a heading printed at 14px and regular weight.
44
+ - **The Tailwind guide no longer imports Tailwind twice.** `/tw` imports Tailwind itself, so it replaces your own `@import "tailwindcss"`; with both, an unminified build carried two copies of Preflight. The focused `/tw/*` entrypoints and `/tw/native` still expect your import first.
45
+ - **`.tooltip-icon` keeps its size on a `<button>`.** The docs now use a real `<button type="button">` as the trigger, since `aria-label` on a role-less `<span>` is not reliably announced; the chip resets the padding and minimum size a button brings under `/native` and `/components`. The docs also no longer call it circular under every aesthetic: it is square under `.neobrutalism` and `.pixel`.
46
+ - **A wrapped `.hint` has room between its lines.** Its line height equalled its 14px size, so wrapped lines touched; it takes `text-sm`'s 20px leading now.
47
+ - **`/palette` follows a light subtree nested in a dark one.** Its light values sat on `:root` alone, so a `.light` region inside `.dark` kept the dark values. They answer the explicit light selectors too now.
48
+ - **An unchecked switch's knob is `CanvasText` in forced colors.** The switch opts out of forced colors to keep its states apart, and its unchecked knob kept the package grey on the system `Canvas`.
49
+ - **A select's chevron mirrors under `dir="rtl"`.** It and the padding kept for it stayed on the right, so a right-to-left value started beside the chevron.
50
+ - **`.btn.icon` follows the size step.** `.sm` left an icon button at the default 40px, and under WebKit a `.lg` one was 40px wide and 48px tall. They are square at 32px and 48px now.
51
+ - **Tables align to the inline start.** `.data-table` cells and its caption used `text-left`, so a right-to-left table stayed left-aligned.
52
+ - **A region of one aesthetic nested inside another keeps its own material.** Every aesthetic now restates the depth colour and clears the focus inset, hover transform, and shadow tokens it does not use, so glass inside `.chunky-tile` no longer casts a black slab under every card, glass inside `.cyber` casts its shadow again, and a region inside `.pixel` no longer inherits pixel's inset focus layer.
@@ -6,6 +6,8 @@ group: Changelog
6
6
 
7
7
  # Changelog
8
8
 
9
+ - [0.6.0](0.6.0.md)
10
+ - [0.5.0](0.5.0.md)
9
11
  - [0.4.0](0.4.0.md)
10
12
  - [0.3.0](0.3.0.md)
11
13
  - [0.2.0](0.2.0.md)
package/docs/concepts.md CHANGED
@@ -12,11 +12,11 @@ Read this before the usage guides. It explains the mental model every component,
12
12
 
13
13
  Every component separates three independent choices, so each can change without a component-specific variant:
14
14
 
15
- | Axis | Question | Classes | Inheritance |
16
- | ------------ | --------------- | --------------------------------------------------------------------------------------------------- | ------------ |
17
- | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
- | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
- | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber` from an optional aesthetic stylesheet | Cascades |
15
+ | Axis | Question | Classes | Inheritance |
16
+ | ------------ | --------------- | -------------------------------------------------------------------------------------------------------------- | ------------ |
17
+ | Intent | Which color? | `.primary`, `.secondary`, `.success`, `.warning`, `.destructive`, `.info` | Element only |
18
+ | Presentation | How much of it? | `.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless` | Cascades |
19
+ | Aesthetic | Made of what? | `.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch` from an optional aesthetic stylesheet | Cascades |
20
20
 
21
21
  ```html
22
22
  <section class="soft pixel">
@@ -66,4 +66,4 @@ The root follows the operating-system `color-scheme` preference until an explici
66
66
 
67
67
  Setting `color-scheme` yourself on an element also re-themes the tokens below it, because that is the only signal the palette reads. This also themes native UI such as scrollbars and form-control internals to match.
68
68
 
69
- This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox 121 for `:has()` (see [Setup](./setup.md#requirements)).
69
+ This mechanism works from Chrome 123, Safari 17.5, and Firefox 120; the package overall requires Firefox 128, for `@property` and `:has()` (see [Setup](./setup.md#requirements)).
package/docs/index.md CHANGED
@@ -5,7 +5,7 @@ description: What @codenhub/styles is, what it covers, and where to go next.
5
5
 
6
6
  # @codenhub/styles
7
7
 
8
- `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Four optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`) restyle that same component set without touching markup.
8
+ `@codenhub/styles` is a CSS-only design system: tokens, a reset, typography and layout utilities, and composable classes for buttons, forms, feedback, surfaces, and tooltips. Every component separates three independent choices — which color, how much of it, and what it's made of — so changing one never requires a component-specific variant. Six optional aesthetics (`.neobrutalism`, `.glass`, `.pixel`, `.chunky-tile`, `.cyber`, `.sketch`) restyle that same component set without touching markup.
9
9
 
10
10
  The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source entrypoints for a project that already runs Tailwind. Both cover the same classes; the difference is which build processes them.
11
11
 
@@ -14,7 +14,7 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
14
14
  - Publishes color, foundation, intent, presentation, and material design tokens as CSS custom properties, themed with `light-dark()`.
15
15
  - Provides a reset, focus-visible handling, and responsive layout/content utilities.
16
16
  - Provides composable classes for buttons, form controls and toggles, alerts, badges, loaders, progress, cards/panels, tables, quotes, code, and tooltips.
17
- - Provides five opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants, each with a solo class that paints the look onto an element the package does not style.
17
+ - Provides six opt-in aesthetics that restyle the same components by setting material tokens, not by adding component variants, each with a solo class that paints the look onto an element the package does not style.
18
18
  - Provides classless mappings so plain HTML elements pick up base styling without added classes, from a dedicated entrypoint.
19
19
  - Publishes Tailwind CSS v4 source entrypoints alongside the compiled output.
20
20
 
@@ -23,9 +23,18 @@ The package ships as ready-to-import compiled CSS, or as Tailwind CSS v4 source
23
23
  - No JavaScript. There is no runtime, no framework bindings, and no build-time code generation beyond the CSS itself.
24
24
  - No semantic HTML, ARIA, keyboard behavior, focus management, validation, or announcement timing. See [Accessibility](./accessibility.md) for the exact line between what the CSS provides and what an application must still add.
25
25
  - No theme-toggling logic. Theme selection is a class or attribute a consumer applies; wiring a toggle to system preference, storage, or a user setting is outside this package. The `@codenhub/theme` package covers that need for consumers who want it.
26
- - No shipped font binaries. `.pixel`, `.chunky-tile`, and `.cyber` accept a consumer-supplied font and fall back to a system stack when none is given.
26
+ - No shipped font binaries. `.pixel`, `.chunky-tile`, `.cyber`, and `.sketch` accept a consumer-supplied font and fall back to a system stack when none is given.
27
27
  - No compatibility layer for removed tokens or classes. Breaking changes are documented, not silently aliased.
28
28
 
29
+ ## Trade-offs
30
+
31
+ Known costs of the design, stated so they can be weighed before adopting it:
32
+
33
+ - **Class names are short and unprefixed.** `.primary`, `.success`, `.dark`, `.light`, `.label`, `.field`, `.hint`, `.section`, `.stack`, `.flat`, and `.ghost` are global names, so another stylesheet on the same page that uses them collides with this one. The package sits in cascade layers, so your own unlayered CSS wins a collision, but a third-party library's styles for the same name still meet the package's.
34
+ - **The complete stylesheets are large before compression.** Minified, `@codenhub/styles` is about 183 KB, `/native` about 251 KB, and `/components` about 145 KB; served compressed they are about 15 KB, 18 KB, and 12 KB with gzip. The repetition that compression removes is the composition every component carries. Import the narrowest entrypoint that covers a page; see [Setup → Import paths](./setup.md#import-paths).
35
+ - **`--color-foreground` is a surface tone, not a text colour.** It is the plate a step off the page that a quiet card or panel rests on. The text colour is `--color-text`.
36
+ - **The success, warning, destructive, and info hover tones are fixed values.** `--color-success-hover` and its three siblings are precomputed rather than mixed from their base colour, because mixing them live crashed WebKit. Overriding `--color-success` alone leaves its hover on the old hue, so retheme the whole family; see [Theming → Color tokens](./usage/theming.md#color-tokens).
37
+
29
38
  ## Next steps
30
39
 
31
40
  - [Setup](./setup.md): Install the package, choose an entrypoint, and render a first component.
@@ -43,7 +43,7 @@ export default function App({ Component, pageProps }: AppProps) {
43
43
  }
44
44
  ```
45
45
 
46
- Running Tailwind CSS v4? `@import "@codenhub/styles/tw"` is a CSS directive, so it belongs in a CSS file, not a bare import in `layout.tsx`/`_app.tsx` — add it (with `@import "tailwindcss"` and, if needed, `@source`) to your global CSS file, and import that file from the same root file shown above instead. See [Tailwind CSS v4](./tailwind.md).
46
+ Running Tailwind CSS v4? `@import "@codenhub/styles/tw"` is a CSS directive, so it belongs in a CSS file, not a bare import in `layout.tsx`/`_app.tsx` — add it (with `@source` if needed; `/tw` imports Tailwind itself, so leave out your own `@import "tailwindcss"`) to your global CSS file, and import that file from the same root file shown above instead. See [Tailwind CSS v4](./tailwind.md).
47
47
 
48
48
  ## Force a theme
49
49
 
@@ -15,20 +15,20 @@ A project that already runs Tailwind CSS v4 can process this package's source in
15
15
  ## Import the source entrypoint
16
16
 
17
17
  ```css
18
- @import "tailwindcss";
19
18
  @import "@codenhub/styles/tw";
20
19
 
21
20
  @source "./src";
22
21
  ```
23
22
 
24
- Tailwind CSS v4 scans your project automatically — most projects need nothing beyond the two imports 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 — `/tw` already ships every class definition it publishes, so scanning it too only slows the build without adding classes.
23
+ `/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).
24
+
25
+ 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 — `/tw` already ships every class definition it publishes, so scanning it too only slows the build without adding classes.
25
26
 
26
27
  ## Add aesthetics
27
28
 
28
29
  Add an aesthetic from the source entrypoint the same way as the compiled one, after the base import:
29
30
 
30
31
  ```css
31
- @import "tailwindcss";
32
32
  @import "@codenhub/styles/tw";
33
33
  @import "@codenhub/styles/tw/aesthetics";
34
34
 
@@ -39,6 +39,14 @@ Add an aesthetic from the source entrypoint the same way as the compiled one, af
39
39
 
40
40
  Import a focused `/tw/*` entrypoint instead of the combined one — such as `/tw/button` or `/tw/form` — when a build only needs one component family; see the full [import path table](../setup.md#import-paths) for what each one composes.
41
41
 
42
+ Unlike `/tw`, the focused entrypoints and `/tw/native` do not import Tailwind, so keep your own import first:
43
+
44
+ ```css
45
+ @import "tailwindcss";
46
+ @import "@codenhub/styles/tw/button";
47
+ @import "@codenhub/styles/tw/loader";
48
+ ```
49
+
42
50
  ## Troubleshooting
43
51
 
44
52
  **Classes resolve to no styling**
@@ -50,7 +58,7 @@ Import a focused `/tw/*` entrypoint instead of the combined one — such as `/tw
50
58
  **Duplicate token and reset rules**
51
59
 
52
60
  - **What happened:** the built stylesheet is larger than expected, and browser devtools show the same custom properties or reset rules defined twice.
53
- - **Why:** importing both a compiled entrypoint (`@codenhub/styles`) and a `/tw` entrypoint (`@codenhub/styles/tw`) in the same build compiles the same tokens and reset from two independent sources; Tailwind's build has no way to know they describe the same package.
54
- - **Fix:** pick one path per project — `/tw` entrypoints for a project running Tailwind CSS v4, compiled entrypoints everywhere else.
61
+ - **Why:** one of two things. Importing both a compiled entrypoint (`@codenhub/styles`) and a `/tw` entrypoint (`@codenhub/styles/tw`) in the same build compiles the same tokens and reset from two independent sources; Tailwind's build has no way to know they describe the same package. Or your stylesheet imports `tailwindcss` beside `/tw`, which already imports it, so Preflight is emitted twice.
62
+ - **Fix:** pick one path per project — `/tw` entrypoints for a project running Tailwind CSS v4, compiled entrypoints everywhere else — and drop your own `@import "tailwindcss"` when you import `/tw`.
55
63
 
56
- Tailwind CSS 4 or newer is required for every `/tw` entrypoint.
64
+ Every `/tw` entrypoint requires Tailwind CSS 4.3.2 or newer, below 5: the source uses `@source not` and `@source inline()`, which arrived in 4.1, and it is tested against 4.3.2.