# Base UI Class System

> **v2** — adopts the *Semantic CSS Framework* grammar (orthogonal adjective categories, no utility classes, runtime-only states, composition over invention) and grafts it onto the bluestack-flavored vocabulary that mirrors AlignUI. Several vocabulary deltas remain open — see [Decisions Pending](#decisions-pending) at the bottom.

---

## TL;DR

A Base UI class string is **one noun followed by orthogonal adjectives, separated by spaces**. Adjectives come from a fixed set of categories. No utility classes in component markup, ever.

```html
<button class="button primary lg cta">Sign up</button>
<div class="alert danger compact dismissible">Payment failed.</div>
<div class="card spacious featured">…</div>
<dl class="description-list cozy">…</dl>
```

---

## The grammar

```
<element class="<noun> [intent] [size] [density] [variant] [orientation] [state] [role]">
```

**Order is convention, not enforcement.** CSS doesn't care; humans scanning diffs do. Keep it left-to-right in the order above.

**One adjective per category.** `primary danger` is invalid. `compact spacious` is invalid. The build (or a lint rule) should refuse contradictions.

**Adjectives that don't apply are silently ignored.** A `divider` ignores intent. An `icon` ignores density. Nothing breaks; the unsupported word does nothing.

**Cascade rules:**
- `intent` and `density` **cascade** to descendants (a `card spacious` makes inner fields spacious by default).
- `size`, `variant`, `orientation`, `state`, `role` are **local** to the element.

---

## The six hard rules

1. **One adjective per category.** Build refuses contradictions like `primary danger`, `xs xl`, `compact spacious`.
2. **Adjectives outside their category are ignored, not errors.** `divider primary` is permissible markup; the `primary` simply has no effect on a divider's CSS.
3. **States are toggled at runtime, not authored.** `loading`, `disabled`, `selected`, `invalid`, `expanded` are added/removed by JS; `dismissible` is the one allowed exception (it's an opt-in *capability*, not a runtime state).
4. **No utility classes in components.** No `mt-4`, `flex-row`, `text-center` inside a component's HTML. Layout utilities on **page wrappers** are tolerated only until `stack`, `row`, `grid`, `bento` are built — then they're forbidden everywhere.
5. **Cascading is a feature.** Set `intent` and `density` once at the surface (card, modal, form) and let descendants inherit; don't restate on every child.
6. **Composition over invention.** A pricing card is `<div class="pricing">`, not a new component. Resist adding nouns until existing vocabulary genuinely cannot describe the thing.

---

## Nouns (components)

The full vocabulary is below. **Built** = exists in bluestack today; **Planned** = accepted name, not yet implemented.

### Forms (13)
`form` · `field` · `label` · `input` · `textarea` · `select` · `combobox` · `checkbox` · `radio` · `switch` · `slider` · `hint` · `error`

### Actions (3)
`button` · `button-group` · `link`

> Replaces the legacy `buttons` plural-container pattern.

### Surfaces (8)
`card` · `panel` · `modal` · `drawer` · `popover` · `tooltip` · `sheet` · `accordion`

### Layout (8)
`stack` · `row` · `grid` · `container` · `divider` · `spacer` · `shell` · `bento`

> `stack` is vertical, `row` is horizontal, `bento` is asymmetric grid. These need to exist before Rule 4 (no utility classes) can be fully enforced.

### Feedback (9)
`badge` · `tag` · `alert` · `toast` · `banner` · `progress` · `spinner` · `skeleton` · `rating`

### Navigation (8)
`nav` · `tab` · `tabs` · `menu` · `breadcrumb` · `pagination` · `steps` · `command`

### Data display (10)
`table` · `list` · `item` · `avatar` · `icon` · `description-list` · `stat` · `calendar` · `feed` · `comment`

### Content patterns (5)
`testimonial` · `pricing` · `product` · `timeline` · `quote`

### Sections (8)
`hero` · `header` · `footer` · `sidebar` · `main` · `section` · `feature` · `faq`

### Media (3)
`figure` · `image` · `embed`

### Typography (6)
`caption` · `body` · `lead` · `title` · `heading` · `display`

> Smallest to largest. `caption` is metadata text; `display` is the largest hero headline.

**Total: ~80 nouns across 11 categories.**

> Multi-word nouns use **kebab-case**: `button-group`, `description-list`. Single-word nouns are bare.

---

## Adjectives (modifiers)

Each category is **closed** — these are the only words allowed.

### Intent (6) — colour / emotion (cascades)

`primary` · `positive` · `negative` · `warning` · `info` · `neutral`

> Mirrors AlignUI's tone vocabulary. `success`/`danger` are accepted as **CSS aliases** of `positive`/`negative` so external Semantic-Framework snippets paste in cleanly; canonical authored markup uses `positive`/`negative`. `secondary` and `muted` are deferred until a real design need surfaces.

### Size (5) — t-shirt scale (local)

`xs` · `sm` · `md` · `lg` · `xl`

> Same word = same *scale step* across components, not the same pixel value. `md` is the default — omit it.

### Density (3) — internal padding (cascades)

`compact` · `cozy` · `spacious`

> `cozy` is the default — omit it. Setting density on a `card` or `form` cascades to its fields.

### Variant (5) — visual style (local)

`filled` · `outline` · `ghost` · `soft` · `link`

> Bluestack keeps `filled` (not `solid`) and `soft` (no replacement in the broader Semantic Framework); `link` is adopted for button-as-link styling. `solid` is accepted as a CSS alias of `filled` so external snippets work.

### Orientation (2) — direction (local)

`horizontal` · `vertical`

> For `tabs`, `steps`, `timeline`, `divider`, `stack`/`row`, `button-group`.

### State (6) — runtime flags (local, JS-toggled)

`active` · `disabled` · `selected` · `loading` · `invalid` · `expanded`

> ARIA-aligned. **Authored states are an anti-pattern** — these are added/removed by your JS framework based on actual state. The one tolerated exception: `dismissible` (opt-in capability, not status).

### Role (3) — emphasis markers (local)

`cta` · `featured` · `empty`

> `cta` for the primary call-to-action, `featured` for highlighted items in a grid, `empty` for empty-state placeholders.

**Total: ~33 adjectives across 7 categories.**

---

## Composition examples

```html
<!-- A primary CTA button at the largest size -->
<button class="button primary lg cta">Get started</button>

<!-- A loading state, JS-toggled -->
<button class="button primary loading" disabled>Saving…</button>

<!-- A spacious card with a featured highlight; intent + density cascade -->
<div class="card spacious featured">
  <h3 class="heading">Pro plan</h3>
  <div class="stat xl primary">$29/mo</div>
  <button class="button primary cta">Choose</button>
</div>

<!-- A vertical stepper -->
<div class="steps vertical">
  <div class="step active">Account</div>
  <div class="step">Billing</div>
  <div class="step">Confirm</div>
</div>

<!-- An alert that can be dismissed (capability), in a danger tone, compact density -->
<div class="alert negative compact dismissible">
  Payment failed. <a class="link">Try again</a>.
</div>

<!-- An invalid input (state authored only as a documentation example;
     in real code your form library toggles `invalid`) -->
<input class="input" aria-invalid="true">
```

---

## Layout: replacing utility classes

Once layout components exist, this is the pattern:

```html
<!-- Old (utility chain in component markup) ❌ -->
<div class="flex flex-col gap-6">
  <div class="flex gap-3 items-center">…</div>
</div>

<!-- New (semantic layout components) ✅ -->
<stack class="spacious">
  <row class="cozy">…</row>
</stack>
```

Until `stack`/`row`/`grid`/`bento` exist as real components, layout utilities on page wrappers are tolerated. They are **not** tolerated inside component CSS or component-shaped markup.

---

## Decisions log (locked 2026-05-07)

All seven open vocabulary deltas have been resolved. The manifesto above already reflects them.

| # | Decision | Resolution |
|---|---|---|
| **D1** | Intent words | Keep `primary/positive/negative/warning/info/neutral`. Accept `success`/`danger` as CSS aliases of `positive`/`negative`. Defer `secondary` and `muted` until needed. |
| **D2** | Size words | Migrate `tiny/small/medium/big/large` → `xs/sm/md/lg/xl`. |
| **D3** | Variant words | Keep `filled/outline/ghost/soft`, add `link`. Accept `solid` as a CSS alias of `filled`. |
| **D4** | Palette colors | Drop from core API. Move the 13 decorative colors (`blue/purple/teal/...`) into an optional `palette.css` that apps opt into. |
| **D5** | `dismissible` | Keep as the one authored-state exception (it's an opt-in capability, not a runtime status). |
| **D6** | `buttons` plural container | Migrate `<div class="buttons">` → `<div class="button-group">`. |
| **D7** | Layout components | Yes — scope a Phase 2 sub-track to ship `stack`, `row`, `grid`, `container`, `divider`, `spacer`, `bento`. Rule 4 stays aspirational until those land. |

---

## Migration sequencing

1. ~~**Sign off on this manifesto.**~~ ✅ Done 2026-05-07.
2. ~~**Aliases pass.**~~ ✅ Done 2026-05-07. Two-track strategy because the Tailwind build is currently broken (circular `@apply rounded-none` in root `input.css:16010`):
   - **CSS source aliases** added at canonical rule sites: `_colors.css` (`.negative` ⇄ `.danger`, `.positive` ⇄ `.success`, `.button.negative` ⇄ `.button.danger`, `.alert.filled` ⇄ `.alert.solid` and friends), `_alert-colors-optimized.css`, `alert.css`, `tag.css`. These activate when a healthy Tailwind build runs.
   - **JS shim** at `/webroot/base/components/aliases.js` (loaded from `index.cfm`) mirrors `.danger`→`.negative`, `.success`→`.positive`, `.solid`→`.filled`, `.button-group`→`.buttons` at page load. Provides live alias support without a build dependency. Removable once CSS aliases reach the deployed `ui.css`.
   - **Known gap:** compound CSS rules like `.alert.filled.warning` were not aliased (would have meant ~50+ selector-list edits). The JS shim adds the canonical class to the element, so `.alert.solid.warning` still works because `.filled` gets added at runtime.
3. ~~**Size-word rename.**~~ ✅ Done 2026-05-07. Mechanical sweep:
   - **CSS source** (`input.css`): word-boundary regex replaced 902 selectors — `.tiny→.xs`, `.small→.sm`, `.big→.lg`, `.large→.xl`. `.medium` had no rules (already implicit default). Utility classes `.text-size-tiny`, `.padding-tiny`, `.gap-tiny` were preserved (different selector shape).
   - **Markup** (`webroot/docs/*.cfm` + `webroot/index.cfm`): 34 files updated. Token-level replacement inside `class="..."` attributes — Tailwind utilities like `font-medium`, `text-sm` (hyphenated, not bare tokens) were preserved. `medium` tokens were dropped (default).
   - **JS shim**: aliases.js extended with `tiny/small/big/large → xs/sm/lg/xl` so legacy authored markup still resolves at runtime.
4. ~~**Layout components.**~~ ✅ Done 2026-05-07. Shipped seven new nouns at the bottom of `input.css` (between `wysiwyg` and the END marker):
   - `stack` (vertical, default cozy gap; `compact`/`spacious`; `horizontal` flips it)
   - `row` (horizontal, items vertically centered; `compact`/`spacious`; `vertical` flips it)
   - `grid` (auto-responsive 1 → 2 → 3 cols; `compact`/`spacious`)
   - `container` (max-w-7xl wrapper; `compact` → max-w-3xl; `spacious` → max-w-screen-2xl) — overrides Tailwind's built-in `.container` utility because component rules sit later in source order.
   - `spacer` (flex-1 by default; `xs/sm/md/lg/xl` for fixed sizes)
   - `shell` (page frame: header / sidebar / main grid areas)
   - `bento` (asymmetric grid; `featured` role spans 2&times;2)
   - `divider` was already in place from a prior pass.
   - Showcase: `/webroot/docs/layout.cfm` (linked from a new "Layout" nav section).
5. ~~**Palette retirement.**~~ ✅ Done 2026-05-07. Hard separation:
   - **Extracted** all 972 palette-only rules + split 40 mixed selector lists out of `input.css` into a new `/palette.css` source file (kept at the project root next to `input.css`). Top-level rules only — no palette rules sat inside `@media`/`@keyframes` blocks, so the extractor was straightforward.
   - **Build**: `palette.css` is built via the same Tailwind pipeline (`npx tailwindcss -i ./palette.css -o webroot/assets/palette.css --minify`). Output: 139 KB.
   - **`ui.css` shrunk** from 824 KB → 710 KB (~14% smaller) — palette rules are no longer in the core bundle.
   - **Docs site**: `webroot/index.cfm` loads both `ui.css` and `palette.css` so showcase pages still render. Production apps that don't want decorative colors include only `ui.css`.
   - The 13 retired words: `red`, `orange`, `yellow`, `olive`, `green`, `teal`, `blue`, `violet`, `purple`, `pink`, `brown`, `grey`, `black`. They are no longer canonical and won't appear in new components.
6. ~~**CLAUDE.md cross-link.**~~ ✅ Done 2026-05-07. The "Architecture Decision" section in `/CLAUDE.md` now links to this file as the canonical reference, and refreshes the inline example to use the new t-shirt sizes (`xs`/`xl`) and a note that decorative palette colors are opt-in.
7. ~~**Rule 4 enforcement (first pass).**~~ ✅ Done 2026-05-08. Conservative sweep of exact-match utility chains across all `webroot/docs/*.cfm` plus `webroot/index.cfm`:
   - **214 conversions across 35 files.**
   - `space-y-1`/`space-y-2` → `stack compact`; `space-y-4` → `stack` (default cozy); `space-y-8` → `stack spacious`.
   - `flex … items-center … gap-2` (± `flex-wrap`) → `row compact` (+ `wrap`); `gap-4` → `row` (+ `wrap`); `gap-8` → `row spacious` (+ `wrap`).
   - Added `.row.wrap { flex-wrap: wrap; }` variant to layout components to support the wrapping cases.
   - **Deferred (no exact mapping):** `space-y-3`/`space-y-6`/`space-y-12` (between cozy and spacious); `gap-3`/`gap-6`; `flex items-center justify-between` (would need a `spacer` insert + structural rewrite, not just a class swap); `inline-flex` patterns; arbitrary `flex-1`/`flex-shrink-0`. Future passes can add `.row.between { justify-content: space-between; }` and similar variants if usage warrants it.

---

## When in doubt

The implementation reference is `/webroot/docs/{component}.cfm`. If the docs page contradicts this manifesto, the docs page wins **and** the manifesto needs an update — open an issue or send a PR amending this file.
