@codenhub/styles 0.1.0 → 0.2.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 (51) hide show
  1. package/README.md +8 -69
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/index.css +1 -1
  4. package/dist/components.css +1 -1
  5. package/dist/index.css +1 -1
  6. package/dist/native.css +1 -1
  7. package/dist/theme.css +1 -1
  8. package/dist/tw/aesthetics/chunky-tile.css +20 -2
  9. package/dist/tw/box.css +45 -2
  10. package/dist/tw/components/button.css +38 -29
  11. package/dist/tw/components/feedback.css +58 -41
  12. package/dist/tw/components/form.css +139 -47
  13. package/dist/tw/components/index.css +16 -6
  14. package/dist/tw/components/loader.css +2 -16
  15. package/dist/tw/components/surface.css +122 -5
  16. package/dist/tw/components/tooltip.css +102 -91
  17. package/dist/tw/elevation.css +57 -14
  18. package/dist/tw/index.css +23 -5
  19. package/dist/tw/intent.css +36 -12
  20. package/dist/tw/native.css +4 -20
  21. package/dist/tw/reset.css +40 -32
  22. package/dist/tw/theme.css +46 -8
  23. package/dist/tw/utilities/content.css +104 -18
  24. package/dist/tw/utilities/index.css +1 -1
  25. package/dist/tw/utilities/layout.css +33 -0
  26. package/docs/accessibility.md +68 -48
  27. package/docs/changelog/0.1.0.md +37 -0
  28. package/docs/changelog/0.1.1.md +10 -0
  29. package/docs/changelog/0.2.0.md +41 -0
  30. package/docs/changelog/index.md +11 -0
  31. package/docs/concepts.md +21 -46
  32. package/docs/index.md +18 -45
  33. package/docs/integrating/astro.md +13 -7
  34. package/docs/integrating/index.md +25 -16
  35. package/docs/integrating/nextjs.md +31 -8
  36. package/docs/integrating/svelte.md +14 -8
  37. package/docs/integrating/tailwind.md +31 -15
  38. package/docs/integrating/vue.md +13 -7
  39. package/docs/setup.md +31 -32
  40. package/docs/usage/aesthetics.md +31 -107
  41. package/docs/usage/buttons.md +24 -36
  42. package/docs/usage/composing.md +31 -102
  43. package/docs/usage/content-and-layout.md +65 -83
  44. package/docs/usage/customizing.md +25 -90
  45. package/docs/usage/feedback.md +25 -56
  46. package/docs/usage/forms.md +48 -80
  47. package/docs/usage/index.md +9 -17
  48. package/docs/usage/theming.md +25 -81
  49. package/docs/usage/tooltips.md +55 -35
  50. package/llms-full.txt +1193 -1510
  51. package/package.json +121 -44
@@ -3,16 +3,20 @@
3
3
  @reference "../box.css";
4
4
  @reference "./surface.css";
5
5
 
6
- /* The host is a positioning wrapper and nothing else. Its bubble is a
7
- pseudo-element placed outside the host's own box, so painting the host would
8
- draw a box around the trigger rather than around the message -- which is
9
- exactly what a blanket surface membership did to this component once already.
10
-
11
- The bubble is the component. It is a surface, so an aesthetic that blurs
12
- surfaces blurs it too, and it rests on a ground of its own, edged, and flat.
13
- Depth is opt-in through `.raised` and `.floating` like every other component;
14
- its boundary against arbitrary content is the floored edge below, not a
15
- shadow. See model.md#elevation. */
6
+ /* The host is a positioning wrapper and nothing else -- it paints no fill, no
7
+ edge, no material of its own. The bubble is a real element beside the
8
+ trigger rather than a pseudo-element attached to it, so intent,
9
+ presentation, aesthetic, and elevation classes go on `.tooltip-bubble`, the
10
+ thing actually shown, not on the wrapper -- the same rule every other
11
+ component in this package follows (theming.md: "Intent does not cascade").
12
+
13
+ Composing the bubble as a real element fixes a real gap, not just a styling
14
+ one: `content: attr(data-tooltip)` text is not reliably exposed to
15
+ assistive tech and cannot be the target of an `aria-describedby`, since that
16
+ needs a real element with a real `id`. A real bubble can carry
17
+ `role="tooltip"` and be referenced by the trigger's own `aria-describedby`;
18
+ wiring that association is the consumer's, this package only styles the
19
+ bubble. See docs/usage/tooltips.md. */
16
20
  @utility tooltip {
17
21
  --tooltip-gap: 0.5rem;
18
22
  --tooltip-top: calc(var(--tooltip-gap) * -1);
@@ -22,34 +26,7 @@
22
26
  --tooltip-origin: bottom;
23
27
  --tooltip-transform: translate(-50%, -100%);
24
28
 
25
- @apply relative;
26
-
27
- /* The trigger badge, for a page with no element to hang the message on. Quiet
28
- -- ghost, edged, on `--intent-subtle`, a key cap's resting shape -- because
29
- it marks that there is a message, it is not one. The line earns its keep:
30
- `--intent-subtle` is a near-page tint that reads the same across families,
31
- so a plate alone left three of eight intents indistinguishable;
32
- `--intent-border` is the slot that differs. Painting the host is right only
33
- here -- everywhere else the bubble is outside the host's box, so painting it
34
- boxes the trigger. `.tooltip-icon` is the opt-in that says the host *is* the
35
- badge. */
36
- &.tooltip-icon {
37
- @apply box inline-flex size-5 cursor-default items-center justify-center text-sm font-bold;
38
-
39
- --_d-fill: 0%;
40
- --_d-fg-on-fill: 0%;
41
- --_d-border: 100%;
42
- --_d-elevation: 0;
43
- --_d-ground: var(--intent-subtle);
44
-
45
- border-radius: var(--ui-radius, calc(infinity * 1px));
46
- /* The one thing `box` hands it that it must refuse. An aesthetic's `clip-path`
47
- clips a host's pseudo-elements along with the host, and this host's
48
- pseudo-element is the bubble, positioned outside its box -- so a stepped
49
- badge under `.pixel` would clip the message away entirely. The bubble
50
- carries the stepped shape itself; the badge stays round. */
51
- clip-path: none;
52
- }
29
+ @apply relative inline-block;
53
30
 
54
31
  &[data-tooltip-position="bottom"] {
55
32
  --tooltip-top: auto;
@@ -72,61 +49,95 @@
72
49
  --tooltip-origin: left;
73
50
  --tooltip-transform: translate(100%, -50%);
74
51
  }
52
+ }
75
53
 
76
- &::after {
77
- @apply surface pointer-events-none absolute z-(--z-popover) w-max max-w-72 px-2.5 py-2 text-sm font-normal whitespace-normal opacity-0 transition-[opacity,transform] duration-(--motion-duration-normal) ease-(--motion-ease);
78
-
79
- /* Filled, over a ground of its own. An intent floods the fill
80
- (`.tooltip.primary` is the primary colour); with no intent `intent.css`
81
- resolves the bubble onto `--color-tooltip` and lifts the neutral cap, a
82
- plate stated per theme because the derived version was a mid-grey slab in
83
- light and lifted in dark. The ground stays the surface tone, not the
84
- plate -- pointed at both, the fill axis went inert and `axes.spec.ts`
85
- caught it. An opaque ground is also what keeps a cascaded `.ghost` from
86
- leaving the bubble boundaryless, retiring the old pinned-fill bound.
87
- `--ui-surface-ground` is read first so a glass tooltip stays glass. */
88
- --_d-fill: 100%;
89
- --_d-fg-on-fill: 100%;
90
- --_d-border: 100%;
91
- /* Flat, and stated rather than left to `box`'s fallback of 1: the bubble is
92
- a surface, so an aesthetic's part-based depth would otherwise reach it
93
- unasked -- `.neobrutalism`'s slab in particular. `.raised`/`.floating`
94
- opt back in. */
95
- --_d-elevation: 0;
96
- --_d-ground: var(--ui-surface-ground, var(--intent-subtle));
97
-
98
- /* The bubble's line is a boundary against content nobody chose, so `box`'s
99
- edge blend is wrong here: at a full fill the line *is* the plate. Restate
100
- the colour only; how much line comes from `--_edge-amount`, floored below. */
101
- --_line: var(--intent-border);
102
-
103
- /* Floored, for the same reason the ground is opaque: a cascade must not take
104
- the boundary from a bubble nobody classed (worst in light, where the plate
105
- is near-white). The element's own `.edgeless` lowers it -- same
106
- element-vs-cascade idiom `text-control` uses -- and `axes.spec.ts` still
107
- sees the edge move because it probes with the class on the element. */
108
- --_edge-floor: 100%;
109
-
110
- /* A bubble is a small transient popover, not a modal, so it takes the
111
- control radius rather than the surface one `surface` hands it. */
112
- border-radius: var(--ui-radius, var(--radius-control));
113
- content: attr(data-tooltip);
114
- left: var(--tooltip-left);
115
- right: var(--tooltip-right);
116
- top: var(--tooltip-top);
117
- bottom: var(--tooltip-bottom);
118
- transform: var(--tooltip-transform) scale(0);
119
- transform-origin: var(--tooltip-origin);
120
- }
121
-
122
- &.edgeless::after {
54
+ /* The bubble. It is a surface, so an aesthetic that blurs surfaces blurs it
55
+ too, and it rests on a ground of its own, edged, and flat -- depth is
56
+ opt-in through its own `.raised`/`.floating`, like any other component, now
57
+ that it is one. No proxy inheritance is needed to carry elevation across a
58
+ host-to-pseudo-element gap: there is no gap, `.tooltip-bubble.raised` reads
59
+ `--ui-elevation` the ordinary way every other component does. See
60
+ model.md#elevation. */
61
+ @utility tooltip-bubble {
62
+ @apply surface pointer-events-none absolute z-(--z-popover) w-max max-w-72 px-2.5 py-2 text-sm font-normal whitespace-normal opacity-0 transition-[opacity,transform] duration-(--motion-duration-normal) ease-(--motion-ease);
63
+
64
+ /* Filled, over a ground of its own. An intent floods the fill
65
+ (`.tooltip-bubble.primary` is the primary colour); with no intent
66
+ `intent.css` resolves the bubble onto `--color-tooltip` and lifts the
67
+ neutral cap, a plate stated per theme because the derived version was a
68
+ mid-grey slab in light and lifted in dark. The ground stays the surface
69
+ tone, not the plate -- pointed at both, the fill axis went inert and
70
+ `axes.spec.ts` caught it. An opaque ground is also what keeps a cascaded
71
+ `.ghost` from leaving the bubble boundaryless. `--ui-surface-ground` is
72
+ read first so a glass tooltip stays glass. */
73
+ --_d-fill: 100%;
74
+ --_d-fg-on-fill: 100%;
75
+ --_d-border: 100%;
76
+ --_d-elevation: 0;
77
+ --_d-ground: var(--ui-surface-ground, var(--intent-subtle));
78
+
79
+ /* The bubble's line is a boundary against content nobody chose, so `box`'s
80
+ edge blend is wrong here: at a full fill the line *is* the plate. Restate
81
+ the colour only; how much line comes from `--_edge-amount`, floored below. */
82
+ --_line: var(--intent-border);
83
+
84
+ /* Floored, for the same reason the ground is opaque: a cascade must not take
85
+ the boundary from a bubble nobody classed (worst in light, where the plate
86
+ is near-white). The element's own `.edgeless` lowers it -- same
87
+ element-vs-cascade idiom `text-control` uses. */
88
+ --_edge-floor: 100%;
89
+
90
+ /* A bubble is a small transient popover, not a modal, so it takes the
91
+ control radius rather than the surface one `surface` hands it. */
92
+ border-radius: var(--ui-radius, var(--radius-control));
93
+ left: var(--tooltip-left);
94
+ right: var(--tooltip-right);
95
+ top: var(--tooltip-top);
96
+ bottom: var(--tooltip-bottom);
97
+ transform: var(--tooltip-transform) scale(0);
98
+ transform-origin: var(--tooltip-origin);
99
+
100
+ &.edgeless {
123
101
  --_edge-floor: 0%;
124
102
  }
103
+ }
125
104
 
126
- &:hover::after,
127
- &:focus-visible::after,
128
- &[data-state="open"]::after {
129
- opacity: 1;
130
- transform: var(--tooltip-transform) scale(1);
131
- }
105
+ /* Shown on hover or focus anywhere in the host, or pinned open with
106
+ `data-state="open"`. `:focus-within` rather than the trigger's own
107
+ `:focus-visible`, the same substitution `.input-group` makes for the
108
+ identical reason: the bubble is a sibling, not a pseudo-element, of the
109
+ trigger, so nothing but the shared ancestor can see both at once. The cost
110
+ is the one `.input-group` already accepts -- the bubble also shows on a
111
+ mouse click, which the old `:focus-visible`-only version did not. */
112
+ .tooltip:hover .tooltip-bubble,
113
+ .tooltip:focus-within .tooltip-bubble,
114
+ .tooltip[data-state="open"] .tooltip-bubble {
115
+ opacity: 1;
116
+ transform: var(--tooltip-transform) scale(1);
117
+ }
118
+
119
+ /* The trigger badge, for a message with no other element to hang it on. Quiet
120
+ -- ghost, edged, on `--intent-subtle`, a key cap's resting shape -- because
121
+ it marks that there is a message, it is not one. The line earns its keep:
122
+ `--intent-subtle` is a near-page tint that reads the same across families,
123
+ so a plate alone left three of eight intents indistinguishable;
124
+ `--intent-border` is the slot that differs.
125
+
126
+ `clip-path: none` used to exist to protect the bubble, a pseudo-element of
127
+ this same host, from being clipped away along with it under an aesthetic
128
+ like `.pixel`. The bubble is independent now, so that reason is gone, but
129
+ the declaration stays: the docs call this a circular fixed identity, not a
130
+ shape a container's aesthetic reaches, the same way `.radio` keeps its own
131
+ circle instead of taking `.pixel`'s stepped silhouette. */
132
+ @utility tooltip-icon {
133
+ @apply box inline-flex size-5 cursor-default items-center justify-center text-sm font-bold;
134
+
135
+ --_d-fill: 0%;
136
+ --_d-fg-on-fill: 0%;
137
+ --_d-border: 100%;
138
+ --_d-elevation: 0;
139
+ --_d-ground: var(--intent-subtle);
140
+
141
+ border-radius: var(--ui-radius, calc(infinity * 1px));
142
+ clip-path: none;
132
143
  }
@@ -1,18 +1,54 @@
1
1
  /* Elevation is a modifier, not an axis: depth is a per-element decision (raised
2
2
  tiles beside a flat panel in one aesthetic), so one unitless number multiplies
3
- the aesthetic's shadow geometry where the component composes it. Unitless so
4
- it inherits -- a container flattens a region, an element opts back in -- and
5
- so `.flat` removes a part-based slab or blur without a conditional (complete
6
- surface shadows like glass remain). The two classes that ask for depth carry
7
- the geometry themselves; on a component it would inherit into everything
8
- nested and a plain page would have a shadow under every card. See
9
- model.md#elevation.
3
+ the aesthetic's shadow geometry where the component composes it. `.flat`
4
+ removes a part-based slab or blur without a conditional (complete surface
5
+ shadows like glass remain). See model.md#elevation.
6
+
7
+ `--ui-elevation`, and the plain-page fallback geometry (`--elevation-y`,
8
+ `--elevation-blur`) below, are registered non-inheriting: a class sets them
9
+ on the element it is written on and nothing else picks them up. They used to
10
+ be ordinary inherited custom properties, on the reasoning that a container
11
+ could flatten or lift a whole region in one class and any element inside
12
+ could opt back out -- but the same mechanism let a `.floating` dialog's
13
+ ambient ×2 reach its own unclassed "Cancel"/"Remove" buttons, doubling their
14
+ shadow with nothing in the markup asking for that. Depth is not the same
15
+ kind of thing as color or material: a button inside a raised card is not
16
+ itself more raised, it is just resting normally inside an already-raised
17
+ surface. `--ui-shadow-*` -- what an aesthetic supplies -- is unaffected and
18
+ still cascades normally; that is a different mechanism (the material tokens
19
+ in `src/aesthetics/*.css`), and an aesthetic still has to reach every nested
20
+ component uniformly the way it always did. Registering these three is what
21
+ scopes the change to the elevation modifier alone.
10
22
 
11
23
  `.flat`/`.raised`/`.floating` and `.elevation-none`/`-sm`/`-md` are
12
24
  interchangeable aliases, the second borrowing the raw `--elevation-*` scale's
13
25
  naming. Bare `sm`/`md`/`lg` would collide with the size modifier;
14
26
  `.elevation-lg` is reserved, not shipped. */
15
27
 
28
+ /* `syntax: "*"` with no `initial-value` is deliberate: a numeric syntax would
29
+ require one (CSS Properties and Values API), and any initial-value at all
30
+ would mean the property is never actually unset on an element that does not
31
+ write it -- which is exactly the state `var(--ui-elevation, var(--_d-elevation,
32
+ 1))` in `box.css` needs to fall through to a component's own default. With
33
+ no initial-value and `inherits: false`, an element that does not set the
34
+ property directly computes it as guaranteed-invalid, the same as if it had
35
+ never been registered at all -- `var()` takes its fallback precisely where
36
+ inheritance used to hand it an ambient value instead. */
37
+ @property --ui-elevation {
38
+ syntax: "*";
39
+ inherits: false;
40
+ }
41
+
42
+ @property --elevation-y {
43
+ syntax: "*";
44
+ inherits: false;
45
+ }
46
+
47
+ @property --elevation-blur {
48
+ syntax: "*";
49
+ inherits: false;
50
+ }
51
+
16
52
  /* No part-based depth. Complete surface shadow values, such as glass, are not
17
53
  reached by the elevation multiplier. */
18
54
  .elevation-none,
@@ -20,20 +56,27 @@
20
56
  --ui-elevation: 0;
21
57
  }
22
58
 
23
- /* The aesthetic's depth as authored, said explicitly. The geometry is the
24
- fallback for a page with no aesthetic in scope; an aesthetic that declares its
25
- own overrides it, and the elevation multiplies whichever one lands. */
59
+ /* The plain-step geometry both rungs multiply, declared once so the two cannot
60
+ drift. It is the fallback for a page with no aesthetic in scope; an aesthetic
61
+ that declares its own shadow parts overrides it, and the elevation multiplier
62
+ scales whichever one lands. `.floating` takes its extra depth from the
63
+ multiplier below, not a second copy of these parts. */
26
64
  .elevation-sm,
27
- .raised {
28
- --ui-elevation: 1;
65
+ .raised,
66
+ .elevation-md,
67
+ .floating {
29
68
  --elevation-y: 1px;
30
69
  --elevation-blur: 3px;
31
70
  }
32
71
 
72
+ /* The aesthetic's depth as authored, said explicitly. */
73
+ .elevation-sm,
74
+ .raised {
75
+ --ui-elevation: 1;
76
+ }
77
+
33
78
  /* Twice it, for menus and popovers. */
34
79
  .elevation-md,
35
80
  .floating {
36
81
  --ui-elevation: 2;
37
- --elevation-y: 1px;
38
- --elevation-blur: 3px;
39
82
  }
package/dist/tw/index.css CHANGED
@@ -1,9 +1,27 @@
1
1
  @import "tailwindcss";
2
- @source not "../dist";
3
- @source not "../tests";
4
- @source not "../dev";
5
- @source not "../debug";
6
- @source not "../playground";
2
+
3
+ /* The build has no markup to scan, so Tailwind's automatic content detection
4
+ sees only this package's own files. The `src/` scan is load-bearing -- it is
5
+ how the composition utilities (`box`, `surface`, `text-control`,
6
+ `input-group`, `loader-mask`) get emitted as standalone rules, since nothing
7
+ `@source inline`s them -- so `source(none)` is not an option. A handful of
8
+ Tailwind utility names also collide with a CSS property or a word this
9
+ file's own comments use (`backdrop-filter`, `filter`, `transition`,
10
+ `transform`, `container`), which reads like a risk of the same scan --
11
+ verified against Tailwind 4.3.2, none of them emit.
12
+ `tests/integration/exports.test.ts` guards it, so a scanner change that
13
+ starts emitting one is caught rather than shipping silently.
14
+
15
+ The `@source not` lines below keep everything that is not `src/` out of that
16
+ scan: the sibling app and fixture directories, the `docs/` tree, the build
17
+ scripts, and the root prose and data files (`README.md`, `llms*.txt`,
18
+ `registry*.json`, `package.json`). Without them a class name or `--color-*`
19
+ reference that appears only in a documentation example ships as real CSS --
20
+ the violet ramp in `docs/usage/theming.md`'s custom-intent example was
21
+ compiling into `dist/index.css` and `dist/native.css`, and any utility in a
22
+ docs code fence did the same. `tests/integration/exports.test.ts` guards it.
23
+ `scripts/build-tw.mjs` strips these lines from the `dist/tw/` copy, where they
24
+ resolve to nothing and the `/tw` consumer scans their own tree anyway. */
7
25
 
8
26
  @import "./theme.css";
9
27
  @import "./typography.css";
@@ -8,6 +8,26 @@
8
8
  tooltips, key caps, code and table heads sit on) and `--intent-border` to
9
9
  `--ui-ink` (how an aesthetic restates the neutral line without naming one). */
10
10
 
11
+ /* The same neutral values, at the root. This is the floor: an element that
12
+ carries no intent class and matches none of the resets below -- a component
13
+ `@apply`-ed onto a selector the package does not name, a bare `<code>` with
14
+ no native stylesheet loaded -- still composes against a real colour instead
15
+ of an undefined one that collapses its `color-mix()` to nothing. It is not
16
+ the barrier. `.neutral` and both resets below still write these slots on the
17
+ element itself, which is a matching rule and so beats this inherited value --
18
+ that is what stops a `.success` container's intent reaching a component
19
+ nested under it. An intent class on the element beats both. Values track
20
+ `.neutral` slot for slot; `registry.test.ts` fails the build on drift. */
21
+ :root {
22
+ --intent-color: var(--color-text);
23
+ --intent-contrast: var(--color-text-contrast);
24
+ --intent-hover: var(--color-text-hover);
25
+ --intent-strong: var(--color-text-strong);
26
+ --intent-subtle: var(--color-surface);
27
+ --intent-border: var(--ui-ink, var(--color-border));
28
+ --intent-fill-max: 20%;
29
+ }
30
+
11
31
  /* Neutral stops its fill short: its colour is the page's ink, so a full fill is
12
32
  a slab and `.neutral.solid` was pixel-for-pixel `.primary.solid`. 20% is
13
33
  chosen against `.soft`'s 12% -- a step past the tint, not a repeat.
@@ -113,7 +133,8 @@
113
133
  .data-table,
114
134
  .pre,
115
135
  .quote,
116
- .tooltip,
136
+ .tooltip-bubble,
137
+ .tooltip-icon,
117
138
  .badge,
118
139
  .kbd,
119
140
  .code,
@@ -148,8 +169,8 @@
148
169
  }
149
170
 
150
171
  /* The bubble a tooltip draws is the one plate in the package that is chosen
151
- rather than composed, so the tooltip resolves the no-intent case onto its own
152
- pair instead of onto the shared neutral. Composed, it was 20% of the page's
172
+ rather than composed, so it resolves the no-intent case onto its own pair
173
+ instead of onto the shared neutral. Composed, it was 20% of the page's
153
174
  ink over `--color-surface`, which lifts off a dark page and sinks into a light
154
175
  one -- see `--color-tooltip` in `theme.css` for the measurements.
155
176
 
@@ -158,17 +179,20 @@
158
179
  rather than an ink, so a full fill of it is the plate and there is nothing to
159
180
  stop short of.
160
181
 
161
- `--intent-subtle` stays the surface token. It is what the trigger badge rests
162
- on, and a badge is a quiet marker on the page rather than the message, so it
163
- keeps the tone every other unclassed component sits on.
182
+ `--intent-subtle` stays the surface token, the same tone every other
183
+ unclassed component sits on. `.tooltip-icon`, the trigger badge, is a
184
+ separate element now and does not read this block at all -- it never fills
185
+ past `0%`, so the generic neutral reset above it is enough, and it keeps its
186
+ own resting ground and line independent of whatever intent the bubble shows.
164
187
 
165
188
  Two selectors, because `none` and `neutral` have to agree here. `:where()`
166
- loses to any intent class, so `.tooltip.primary` still floods with primary;
167
- `.tooltip.neutral` carries 0-2-0 and beats `.neutral`, so writing the intent
168
- out gives the same bubble as writing nothing. Without the second selector the
169
- two would differ on a component where they mean the same thing. */
170
- :where(.tooltip),
171
- .tooltip.neutral {
189
+ loses to any intent class, so `.tooltip-bubble.primary` still floods with
190
+ primary; `.tooltip-bubble.neutral` carries 0-2-0 and beats `.neutral`, so
191
+ writing the intent out gives the same bubble as writing nothing. Without the
192
+ second selector the two would differ on a component where they mean the
193
+ same thing. */
194
+ :where(.tooltip-bubble),
195
+ .tooltip-bubble.neutral {
172
196
  --intent-color: var(--color-tooltip);
173
197
  --intent-contrast: var(--color-tooltip-contrast);
174
198
  --intent-hover: var(--color-tooltip);
@@ -1,10 +1,8 @@
1
1
  @import "./theme.css";
2
2
  @reference "tailwindcss";
3
- @source not "../dist";
4
- @source not "../tests";
5
- @source not "../dev";
6
- @source not "../debug";
7
- @source not "../playground";
3
+ /* Everything that is not `src/` -- see the note in `index.css`. Without the
4
+ docs and root-file excludes a utility used only in a documentation example
5
+ compiles into `dist/native.css`. */
8
6
  @import "./typography.css";
9
7
  @import "./utilities/index.css";
10
8
  @import "./components/index.css";
@@ -52,21 +50,7 @@
52
50
  }
53
51
 
54
52
  @layer base {
55
- h1,
56
- h2,
57
- h3,
58
- h4,
59
- h5,
60
- h6,
61
- p,
62
- figure,
63
- blockquote,
64
- dl,
65
- dd,
66
- ul,
67
- ol {
68
- margin: 0;
69
- }
53
+ /* The `h1`-`ol` margin reset lives in `reset.css`, imported above. */
70
54
 
71
55
  /* Headings */
72
56
  h1 {
package/dist/tw/reset.css CHANGED
@@ -3,16 +3,13 @@
3
3
  @import "./typography.css";
4
4
 
5
5
  @layer base {
6
- /* Every sizing utility this package ships — `min-height`/`min-width:
7
- var(--control-height)` on `.btn`, `.ipt`, `.select`, `.textarea` chief
8
- among them — means that as the control's total footprint, padding and
9
- border included. Without this, `apps/docs` reads correctly only because
10
- its own Tailwind pipeline supplies Preflight's border-box reset
11
- separately; this package only pulls in Tailwind's utility engine via
12
- `@reference`, never its Preflight, so a consumer with no Tailwind
13
- pipeline of its own — `apps/demo` imports this compiled stylesheet
14
- directly — got the content-box default instead, inflating every sized
15
- control by its own padding and border. */
6
+ /* The package's own components no longer lean on this: `@utility box` sets
7
+ `box-sizing: border-box` on itself, so a sized control is right in every
8
+ entrypoint including the reset-less `/components` and granular `/tw/*`
9
+ ones. What is left for the universal rule is the consumer's own markup --
10
+ a plain `<div>` laid out beside our components under the full reset -- so
11
+ it stays part of the opinionated baseline the reset already is, rather
12
+ than a correctness prop the components need. */
16
13
  *,
17
14
  *::before,
18
15
  *::after {
@@ -36,7 +33,7 @@
36
33
 
37
34
  html,
38
35
  body {
39
- @apply bg-background text-text font-default min-h-dvh;
36
+ @apply bg-background text-text font-default;
40
37
  }
41
38
 
42
39
  h1,
@@ -55,10 +52,6 @@
55
52
  margin: 0;
56
53
  }
57
54
 
58
- body {
59
- @apply flex flex-col;
60
- }
61
-
62
55
  button,
63
56
  input,
64
57
  optgroup,
@@ -74,10 +67,6 @@
74
67
  @apply enabled:cursor-pointer;
75
68
  }
76
69
 
77
- img {
78
- @apply select-none;
79
- }
80
-
81
70
  ::selection {
82
71
  background: var(--color-primary);
83
72
  color: var(--color-primary-contrast);
@@ -124,19 +113,38 @@
124
113
  }
125
114
  }
126
115
 
127
- @layer components {
128
- [aria-current="page"] {
129
- @apply text-primary font-bold;
116
+ /* Unlayered on purpose, matching the `forced-colors` blocks in `native.css` and
117
+ `form.css`. An accessibility override has to win over any layered rule
118
+ whatever the specificity, and a `@layer components` wrapper here would let the
119
+ component utilities that draw a border overrule it. */
120
+ @media (forced-colors: active) {
121
+ .btn,
122
+ .ipt,
123
+ .textarea,
124
+ .select,
125
+ .alert,
126
+ .badge {
127
+ border-color: CanvasText;
130
128
  }
129
+ }
131
130
 
132
- @media (forced-colors: active) {
133
- .btn,
134
- .ipt,
135
- .textarea,
136
- .select,
137
- .alert,
138
- .badge {
139
- border-color: CanvasText;
140
- }
141
- }
131
+ /* The user agent already promises both of these for a native `<dialog>` --
132
+ `dialog:not([open]) { display: none }` and a centered `margin: auto` on the
133
+ open box -- but both are user-agent origin, which loses to any author rule
134
+ at all. `.card`'s own `display: block` (`surface`'s `@apply box block`)
135
+ beats the first outright, so a `<dialog class="card">` renders open with no
136
+ `open` attribute and no `.showModal()` call -- the registry has no
137
+ dialog/modal component of its own, and `.card` is the closest primitive a
138
+ consumer reaches for. A margin reset on every element -- Tailwind's own
139
+ Preflight on the entries that carry it -- beats the second, so an opened
140
+ dialog pins to the top-left instead of centering. Restated here, unlayered like
141
+ the block above, for the same reason: a behavior override has to win over
142
+ any layered rule whatever the specificity, and `.card` is layered in the
143
+ utilities layer regardless of how it composes `box`. */
144
+ dialog:not([open]) {
145
+ display: none;
146
+ }
147
+
148
+ dialog[open] {
149
+ margin: auto;
142
150
  }