# HTMX CSS Generator — Known Gaps & Fixes Running log of edge cases discovered while consuming `dist/greyhaven.htmx.css` in framework-agnostic (HTMX / Go `html/template` / etc.) projects. Each entry captures: the bug, its root cause, and the fix. Keep this file updated when new gaps surface so consumers have a single place to check. --- ## Fixed ### 2026-04-24 — `toggle-group-item` missing Toggle base styles **Symptom (consumer-side):** `data-slot="toggle-group-item"` renders as unstyled inline text — no padding, no height, no border, and `data-state="on"` produces no visual change. **Root cause:** In React, `ToggleGroupItem` composes `toggleVariants()` with a small set of segmented-group overrides: ```tsx className={cn( toggleVariants({ variant, size }), // base + variant + size classes 'min-w-0 flex-1 shrink-0 rounded-none ...' )} ``` The generator's `extractSlot` only captured the first *string-literal* arg of `cn(...)` — the segmented overrides — and never followed the `toggleVariants(...)` call to pull in Toggle's base, variant, and size rules. As a result, the emitted `[data-slot="toggle-group-item"]` had no padding, hover, `disabled`, or — critically — the `data-[state=on]:bg-accent data-[state=on]:text-accent-foreground` rule that drives the pressed state. **Fix (`scripts/generate-htmx-css.ts`):** `SlotExtract` now carries a `viaVariants: string[]` field. When processing `className={cn(xVariants(...), 'literal')}`, the extractor records every `*Variants` call it sees. The emitter then merges each referenced CVA's base + variant rules under the slot's own selector, so a slot that *composes* another component's variant system inherits its full rule set. **Generated output before:** ```css [data-slot="toggle-group-item"] { /* only overrides */ min-w-0 flex-1 ... rounded-none first:rounded-l-md ... } ``` **Generated output after:** ```css [data-slot="toggle-group-item"] { /* full Toggle base + item overrides */ inline-flex items-center ... data-[state=on]:bg-accent ... min-w-0 flex-1 ... rounded-none first:rounded-l-md ... } [data-slot="toggle-group-item"][data-variant="outline"] { /* inherited variant */ } [data-slot="toggle-group-item"][data-size="sm"] { /* inherited size */ } ``` **Consumer impact:** existing HTMX demos do not use `toggle-group-item`, so no screenshot-diff regression risk. Consumers previously working around this by using `data-slot="toggle"` on each item (with manual rounded-none / border overrides) can switch to the clean `toggle-group-item` form. --- ### 2026-04-24 — Toggle active state uses `primary` (orange), not `accent` (grey) **Symptom:** Design review flagged the active-state grey (`bg-accent`) as too muted — users expected the brand orange for selected items in a segmented control, matching every other "selected" affordance in the system (primary button, active nav link, focus ring). **Fix:** `components/ui/toggle.tsx` — `toggleVariants` base class swapped from `data-[state=on]:bg-accent data-[state=on]:text-accent-foreground` to `data-[state=on]:bg-primary data-[state=on]:text-primary-foreground`. This propagates to `ToggleGroupItem` automatically (composes `toggleVariants`) and to the generated `[data-slot="toggle"]` and `[data-slot="toggle-group-item"]` rules after `pnpm htmx-css:build && pnpm htmx-demo:build`. **Rationale:** Greyhaven's palette reserves `accent` for hover hints and subtle surface shifts; `primary` is the single brand-accent color, used wherever a choice is committed. Selected-state for Toggle/ToggleGroup now matches that convention. ### 2026-04-24 — `ToggleGroupItem` overflow: text escapes the bg box **Symptom:** With segmented labels of uneven length ("System" / "Light" / "Dark"), the longer label's text rendered outside its button's background rectangle. Selected-state highlight appeared narrower than the text it was supposed to cover, and hover state was clipped for longer labels. **Root cause:** `ToggleGroupItem` layered `'min-w-0 flex-1 shrink-0'` on top of `toggleVariants`' size-based `min-w-*`. Because tailwind-merge keeps the later `min-w-0`, each item was allowed to shrink below its content. Combined with `flex: 1 1 0%` and `w-fit` on the group, items ended up forced to equal narrow columns sized to `container_width / N` — which for the "System" case was ~37px, well below the text's ~45px intrinsic width. `whitespace-nowrap` then let the text bleed out of the button's layout box. **Fix:** `components/ui/toggle-group.tsx` — dropped `min-w-0 flex-1 shrink-0` from `ToggleGroupItem`'s override string. Items now size to their content (respecting the `min-w-*` floor from `toggleVariants`), the group's `w-fit` sums them up, and padding is symmetric. Unequal-width items are the intentional result (a segmented "System/Light/Dark" now shows "System" wider than "Light"/"Dark"); if a consumer specifically wants equal-width columns, they can re-apply `flex-1 basis-0` on each item via `className`. **Consumer impact:** no regressions in the existing 21 sections; the new `toggle-group` section passes at 99.98%. ### 2026-04-24 — Toggle horizontal padding aligned with Button **Symptom:** Side-by-side, a `Toggle` (or `ToggleGroupItem`) looked cramped compared to a regular `Button` of the same size — the active-state orange bg sat tight against the label, while Button had comfortable breathing room on both sides. Perceived as a visual-rhythm bug across any UI that mixed the two in the same view. **Root cause:** `Toggle`'s cva had roughly half the horizontal padding of `Button` per size: | size | Button | Toggle (old) | |---------|---------------|---------------| | default | `px-4` (16px) | `px-2` (8px) | | sm | `px-3` (12px) | `px-1.5` (6px)| | lg | `px-6` (24px) | `px-2.5` (10px)| **Fix:** `components/ui/toggle.tsx` — `toggleVariants.size.*` horizontal padding now matches Button exactly. Also added `has-[>svg]:px-*` for icon-only toggles, mirroring Button's affordance. `min-w-*` kept as a floor so very short labels (`A`, `B`) still render as balanced pills. ```ts size: { default: 'h-9 px-4 min-w-9 has-[>svg]:px-3', sm: 'h-8 px-3 min-w-8 has-[>svg]:px-2.5', lg: 'h-10 px-6 min-w-10 has-[>svg]:px-4', }, ``` ## Known (not yet fixed) *(Add entries here as they are discovered. Template:* ``` ### YYYY-MM-DD — **Symptom:** ... **Root cause:** ... **Workaround:** ... **Proposed fix:** ... ``` *)* ### Radix primitive-only components (AlertDialogAction, AlertDialogCancel) **Symptom:** These components have no static `data-slot` in their JSX — they wrap `` which sets `data-slot` at runtime. The AST extractor never sees them, so no CSS is emitted. **Workaround:** Consumers should add `data-slot="button"` on the corresponding HTML element. The visual contract matches Button exactly (`cn(buttonVariants(), className)` in React). **Proposed fix:** none yet — requires either parsing the Radix primitive source, or adding explicit `data-slot` attributes upstream. ### Interactive components without a built-in JS bridge **Symptom:** `data-slot="select-trigger"`, `data-slot="tooltip-content"`, `data-slot="dialog-content"`, `data-slot="popover-content"` emit the CSS for their open/closed states, but nothing drives them. **Workaround:** Consumers must implement open/close + positioning themselves (Alpine.js, plain JS, HTMX swap). `public/htmx.html` ships a ~60-line vanilla bridge for checkbox / switch / tabs; select / tooltip / dialog remain consumer concerns. **Proposed fix:** ship an opt-in `greyhaven.htmx.js` companion with pointer positioning (e.g. via Floating UI) and focus management.