# Extension CSS Contract

> **Version**: v0.6.13 — Responsive & Spacing

This document defines the CSS contract between the Armature kernel and extension
packages. Extensions **must** follow these rules; the kernel guarantees the listed
classes and variables are stable public API.

---

## Naming Rule

All class selectors in extension CSS (`packages/{slug}/css/main.css`) must start
with `.ext-{slug}-`. The `{slug}` is the package directory name.

```css
/* Good */
.ext-my-app-sidebar { ... }
.ext-my-app-card { ... }

/* Bad — will be rejected by the linter */
.sidebar { ... }
.my-sidebar { ... }
```

**Compound selectors**: Descendant classes scoped under your `.ext-{slug}` root
are allowed to reference kernel classes or state modifiers:

```css
/* Allowed — kernel class scoped under extension namespace */
.ext-my-app .sw-btn { margin-top: 8px; }

/* Allowed — state modifier on an extension element */
.ext-my-app-item.active { ... }
```

Run `bash scripts/lint-package-css.sh` to validate. The linter checks that the
**first** class selector in every rule starts with `.ext-{slug}`.

---

## Stable Kernel Classes

Extensions may reference these classes in compound selectors. They are part of
the public API and will not change without a major version bump.

### Components (from `sw-primitives.css`)

| Class pattern | Component |
|---------------|-----------|
| `.sw-btn`, `.sw-btn--{variant}`, `.sw-btn--{size}` | Buttons |
| `.sw-input` | Text inputs |
| `.sw-field`, `.sw-field__label`, `.sw-field__hint` | Form fields |
| `.sw-dialog`, `.sw-dialog__header`, `.sw-dialog__body`, `.sw-dialog__footer` | Dialogs |
| `.sw-toast`, `.sw-toast-container` | Toast notifications |
| `.sw-menu`, `.sw-menu-item` | Context menus |
| `.sw-tabs`, `.sw-tab-btn` | Tab strips |
| `.sw-dropdown` | Custom dropdowns |
| `.sw-spinner` | Loading spinners |
| `.sw-avatar` | User avatars |
| `.sw-drawer` | Slide-out drawers |
| `.sw-banner` | Banner bars |
| `.sw-tooltip` | Tooltips |

### Extension Mount

The extension surface container has a `data-ext` attribute set to the package
slug. Use this for scoping if needed:

```css
[data-ext="my-app"] .ext-my-app-sidebar { ... }
```

---

## Stable CSS Variables

All variables from `variables.css` are public API. Extensions should use these
instead of hardcoded colors to respect the user's theme.

### Colors

| Variable | Purpose |
|----------|---------|
| `--bg` | Page background |
| `--bg-secondary` | Secondary/darker background |
| `--bg-elevated` | Elevated surface background |
| `--bg-raised` | Raised card background |
| `--bg-surface` | Surface-level background |
| `--bg-hover` | Hover state background |
| `--bg-active` | Active/pressed state background |
| `--bg-code` | Code block background |
| `--text` | Primary text color |
| `--text-2` | Secondary text color |
| `--text-3` | Tertiary/muted text color |
| `--text-on-color` | Text on colored backgrounds |
| `--accent` | Primary accent color |
| `--accent-dim` | Dimmed accent for backgrounds |
| `--accent-hover` | Accent hover state |
| `--accent-light` | Light accent variant |
| `--border` | Default border color |
| `--border-light` | Light border variant |
| `--border-elevated` | Border for elevated surfaces |
| `--danger` | Error/destructive color |
| `--danger-dim` | Dimmed danger background |
| `--danger-light` | Light danger variant |
| `--success` | Success/positive color |
| `--success-dim` | Dimmed success background |
| `--success-light` | Light success variant |
| `--warning` | Warning/caution color |
| `--warning-dim` | Dimmed warning background |
| `--warning-light` | Light warning variant |
| `--purple` | Purple accent |
| `--purple-dim` | Dimmed purple background |

### Spacing

Use spacing tokens instead of hardcoded values for padding, margin, and gap.
For sub-4px values (1px, 2px, 3px) used in borders and fine detail, hardcoded
values are acceptable.

| Variable | Value | Computed |
|----------|-------|---------|
| `--sp-1` | `0.25rem` | 4px |
| `--sp-1h` | `0.375rem` | 6px |
| `--sp-2` | `0.5rem` | 8px |
| `--sp-2h` | `0.625rem` | 10px |
| `--sp-3` | `0.75rem` | 12px |
| `--sp-4` | `1rem` | 16px |
| `--sp-5` | `1.25rem` | 20px |
| `--sp-6` | `1.5rem` | 24px |
| `--sp-8` | `2rem` | 32px |
| `--sp-10` | `2.5rem` | 40px |
| `--sp-12` | `3rem` | 48px |

Example:

```css
.ext-my-app-card {
    padding: var(--sp-3) var(--sp-4);   /* 12px 16px */
    gap: var(--sp-2);                    /* 8px */
    margin-bottom: var(--sp-4);          /* 16px */
}
```

### Layout & Typography

| Variable | Purpose |
|----------|---------|
| `--font` | Primary font family (self-hosted, no external requests) |
| `--mono` | Monospace font family (self-hosted) |
| `--radius-sm` | Small border-radius (4px) — badges, inline controls |
| `--radius` | Default border-radius (8px) — buttons, inputs, cards |
| `--radius-lg` | Large border-radius (12px) — modals, dialogs, large cards |
| `--transition` | Default transition timing |
| `--shadow-lg` | Large elevation shadow |
| `--overlay` | Modal overlay color |
| `--glass` | Glassmorphism backdrop |
| `--input-bg` | Form input background |
| `--sidebar-w` | Sidebar width |

---

## Responsive Breakpoints

The kernel uses these standard breakpoints. Extensions should use the same
values for consistency.

| Name | Media Query | Use Case |
|------|-------------|----------|
| Mobile | `@media (max-width: 768px)` | Phone-sized, single column |
| Tablet | `@media (max-width: 1024px)` | Tablet/small laptop, narrower sidebars |
| Desktop | Default (no query) | Full layout |

CSS custom properties cannot be used in `@media` queries — use the pixel
values directly.

---

## What Is Internal

Everything not listed above is **internal kernel CSS** and may change between
minor versions. Extensions must not depend on:

- Kernel layout classes (`.admin-*`, `.surface-*`, `.sidebar`, etc.)
- Kernel CSS file load order
- Specific HTML structure of the shell or topbar
- Undocumented CSS variables

---

## Enforcement

The linter script `scripts/lint-package-css.sh` runs against all
`packages/*/css/main.css` files. It exits non-zero if any rule's first class
selector does not start with `.ext-{slug}`.
