# nodes

id: contract:composite:ren-context-menu
type: contract
name: ren-context-menu component.md
path: components/composites/ren-context-menu/component.md
data: {"kind":"composite"}
body:
# ren-context-menu Component Contract

Contextual action menu opened from a target or pointer interaction.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-context-menu` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-context-menu` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Context Menu composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "A target area needs a right-click (or long-press) action menu that opens at the pointer."
    - "The menu hosts imperative actions (cut / copy / paste / delete) rather than navigation."
    - "You want the native Popover API plus the shared ren-menu item styling."
    - "Positioning should follow the pointer instead of a static anchor."
    - "A scoped trigger region is needed (declared via data-context=\"<menu-id>\")."
  avoidWhen:
    - "The menu opens from a button click — use ren-menu (dropdown) instead."
    - "The trigger is a hover affordance — use ren-popover / ren-hover-card."
    - "The menu is the primary navigation — use ren-menubar / ren-sidebar."
    - "You need cascading submenus — use ren-menu (supports nested patterns)."

canonicalImports:
  css:
    - "rends/components/composites/ren-context-menu/ren-context-menu.css"
    - "rends/components/composites/ren-menu/ren-menu.css"
  js:
    - "rends/components/composites/ren-context-menu/ren-context-menu.js"
  notes:
    - "If the page already imports rends/components/index.css, do not import the CSS again."
    - "ren-menu.css must be present alongside this CSS — the menu items (.ren-menu-item, .ren-menu-separator) live there and are reused."
    - "JS handles contextmenu event interception, pointer-based positioning, and popover open/close."

requiredMarkup:
  - "Declare the trigger region with data-context=\"<menu-id>\" on any element; do not use inline oncontextmenu attributes."
  - "Menu root is <ren-context-menu id=\"<menu-id>\">; the component applies the .ren-context-menu, role=menu, and manual popover semantics."
  - "Items are real <button class=\"ren-menu-item\"> elements; separators are <hr class=\"ren-menu-separator\">."
  - "Destructive actions use .ren-menu-item-danger (defined in ren-menu.css); do not invent custom danger classes."
  - "Position is set by JS — do not write inline top/left on the menu element."

forbiddenPatterns:
  - "Opening the menu via custom JS that calls .style.display = 'block' — use showPopover() so the API handles light dismiss + the top layer."
  - "<div role=\"menuitem\" tabindex=\"0\"> styled as a button; use a real <button class=\"ren-menu-item\">."
  - "Static anchored positioning (top: 0; left: 0) — context menus must follow the contextmenu event coordinates."
  - "Suppressing the native context menu globally (document.oncontextmenu = e => e.preventDefault()) — scope prevention to data-context regions only."
  - "Hardcoded box-shadow / hex backgrounds; use --shadow-lg and --color-surface."

tokenPolicy:
  allowed:
    - "Semantic tokens: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-danger, --color-accent."
    - "Layout / motion tokens: --space-*, --stroke-1, --radius-lg, --shadow-lg, --duration-enter, --ease-enter."
    - "Reuses --ren-menu-* tokens defined by ren-menu (item padding, hover bg, etc.) — override at the ren-menu scope."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Defining new --ren-context-menu-* component tokens — the component inherits from ren-menu intentionally."

accessibility:
  required:
    - "Menu root has role=\"menu\" (set by JS) and each .ren-menu-item has role=\"menuitem\"; do not nest interactive controls inside items."
    - "Keyboard: Arrow Up/Down moves focus across items, Home/End jump to first/last, Escape closes the menu, Enter / Space activates."
    - "Trigger must be reachable by keyboard via Shift+F10 (or the dedicated context-menu key) — never gate the menu behind right-click only."
    - "Disabled items set aria-disabled=\"true\" AND skip in keyboard navigation order."
    - "Focus moves into the menu on open and returns to the trigger on close — preserve this when extending behavior."
    - "Reduced motion: open transition is disabled under prefers-reduced-motion (already handled)."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-context-menu/ren-context-menu.css">
<script type="module" src="rends/components/composites/ren-context-menu/ren-context-menu.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div data-context="editor-actions" tabindex="0">Right-click me</div>
<ren-context-menu id="editor-actions">
  <button class="ren-menu-item" role="menuitem">Copy</button>
  <button class="ren-menu-item" role="menuitem">Paste</button>
</ren-context-menu>
```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-context-menu`
- `.ren-menu-item`
- `.ren-menu-separator`
- `.ren-open`

## States And Attributes

- `[aria-disabled]`
- `[data-context]`
- `trigger-id`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-context-menu/ren-context-menu.css`
- `components/composites/ren-context-menu/ren-context-menu.js`
- `docs/components/ren-context-menu.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-date-picker
type: contract
name: ren-date-picker component.md
path: components/composites/ren-date-picker/component.md
data: {"kind":"composite"}
body:
# ren-date-picker Component Contract

Date input composite using a calendar popover.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-picker` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-date-picker` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Date Picker composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User needs to pick a single date (or a single contiguous range) from a calendar popover."
    - "Trigger should be a button-like control that opens a positioned popover (CSS anchor positioning with position-area) with an embedded <ren-calendar>."
    - "The form needs an ISO-string value submitted via a hidden input under a given name."
    - "Preset shortcuts (Today, Tomorrow, This Week) should appear alongside the calendar grid."
    - "Mobile must adapt to a bottom sheet automatically; desktop floats below the trigger."
  avoidWhen:
    - "You need two independent start/end inputs with apply/cancel — use ren-date-range-picker."
    - "You only need a raw native <input type=\"date\"> with no popover styling — fall back to base/primitive-zero."
    - "You need a non-date generic dropdown — use ren-menu, ren-select, or ren-popover."
    - "You need a multi-month calendar surface without an input trigger — render <ren-calendar> directly."

canonicalImports:
  css:
    - "rends/components/composites/ren-date-picker/ren-date-picker.css"
  js:
    - "rends/components/composites/ren-date-picker/ren-date-picker.js"
  notes:
    - "Also requires ren-calendar to be registered (the component instantiates <ren-calendar> inside its dropdown)."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <ren-date-picker> as the host element so the upgrade lifecycle wires up the trigger, dropdown, and hidden input."
  - "Trigger must be a real <button class=\"ren-date-picker-trigger\"> with type=\"button\", aria-haspopup=\"dialog\", and aria-expanded; the component sets these if it renders the trigger itself."
  - "The dropdown surface is a <div class=\"ren-date-picker-dropdown\" popover=\"manual\"> — keep the popover attribute so showPopover()/hidePopover() work."
  - "Inside the dropdown render a <ren-calendar> child; the host syncs its mode/locale/min/max attributes onto it."
  - "If you need form submission, set name=\"…\" on <ren-date-picker> so the auto-injected <input type=\"hidden\"> carries the ISO value."
  - "Use placement=\"bottom\" by default; the host and .ren-date-picker-dropdown mirror the preferred side to data-side."

forbiddenPatterns:
  - "Styling a <div> or <span> as the trigger instead of a real <button> — keyboard activation and aria-expanded depend on the button."
  - "Replacing the inner <ren-calendar> with raw markup — the host wires ren-date-select events directly to the calendar element."
  - "Hardcoding popover positioning offsets via inline styles — rely on anchor-name: --ren-date-picker-anchor, position-anchor, and position-area in the stylesheet."
  - "Toggling the dropdown by setting display: none — use open()/close()/toggle() on the host so aria-expanded and popover state stay in sync."
  - "Wrapping a native <input type=\"date\"> in .ren-date-picker — the component expects to own its trigger and popover."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width."
    - "Semantic surface tokens used by the trigger / dropdown: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-accent, --color-danger, --color-success."
    - "Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / rgb() colors for trigger or dropdown chrome; theme via --color-* and the --ren-calendar-* token API."
    - "Custom transition/animation durations; reuse --duration-enter and --ease-enter so reduced-motion overrides apply."

accessibility:
  required:
    - "Trigger is a real <button type=\"button\"> with aria-haspopup=\"dialog\" and aria-expanded that flips true/false as the dropdown opens and closes."
    - "Trigger min-height is var(--touch-min) so the tap target meets 44px; do not reduce it for touch contexts."
    - ":focus-visible on the trigger and presets must show the --color-accent outline; do not remove it without restoring an equivalent ring."
    - "Escape closes the dropdown; Enter/Space on the trigger opens it; opening focuses the first non-disabled .ren-calendar-day."
    - "Error/success states (.ren-date-picker-error, .ren-date-picker-success) must be paired with a text message — do not rely on border color alone."
    - "Closing returns focus to the trigger; do not break this contract when overriding open()/close()."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-date-picker/ren-date-picker.css">
<script type="module" src="rends/components/composites/ren-date-picker/ren-date-picker.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-date-picker name="start-date" placement="bottom"><button class="ren-date-picker-trigger" type="button" aria-haspopup="dialog" aria-expanded="false">Choose date</button><div class="ren-date-picker-dropdown" popover="manual"><ren-calendar></ren-calendar></div></ren-date-picker>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-calendar`
- `.ren-date-picker`
- `.ren-date-picker-dropdown`
- `.ren-date-picker-empty`
- `.ren-date-picker-error`
- `.ren-date-picker-preset`
- `.ren-date-picker-presets`
- `.ren-date-picker-range`
- `.ren-date-picker-success`
- `.ren-date-picker-trigger`
- `.ren-date-picker-value`

## States And Attributes

- `[data-side]`
- `placement`
- `:active`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-calendar-bg`
- `--ren-calendar-border`
- `--ren-calendar-day-size`
- `--ren-calendar-radius`
- `--ren-calendar-range-bg`
- `--ren-calendar-selected-bg`
- `--ren-calendar-selected-color`
- `--ren-calendar-today-bg`
- `--ren-calendar-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-date-picker/ren-date-picker.css`
- `components/composites/ren-date-picker/ren-date-picker.js`
- `docs/components/ren-date-picker.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-date-range-picker
type: contract
name: ren-date-range-picker component.md
path: components/composites/ren-date-range-picker/component.md
data: {"kind":"composite"}
body:
# ren-date-range-picker Component Contract

Two-date selection composite for ranges.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-date-range-picker` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-date-range-picker` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Date Range Picker composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User needs to pick a contiguous date range (start + end) confirmed by an explicit Apply action."
    - "UI needs dual side-by-side calendars (left = start month, right = next month) inside a CSS-anchor-positioned popover."
    - "Preset shortcuts like Last 7 / Last 30 / This Month / Last Month should be selectable as a sidebar listbox."
    - "Form submission needs two ISO-string fields named `<name>-start` and `<name>-end` via injected hidden inputs."
    - "Selection has Apply/Cancel semantics — draft state on outside-click or Cancel must revert to the previously confirmed range."
  avoidWhen:
    - "Only a single date is needed — use ren-date-picker (mode=\"single\")."
    - "User picks two unrelated dates (start-of-trip, end-of-warranty) that are not a contiguous range — use two ren-date-picker instances."
    - "Range needs commit-on-click without Apply/Cancel — use ren-date-picker with mode=\"range\"."
    - "No calendar UI is needed; a quick \"last N days\" preset is enough — render ren-select / ren-menu of presets only."

canonicalImports:
  css:
    - "rends/components/composites/ren-date-range-picker/ren-date-range-picker.css"
  js:
    - "rends/components/composites/ren-date-range-picker/ren-date-range-picker.js"
  notes:
    - "Also requires ren-calendar to be registered — the component instantiates two <ren-calendar mode=\"range\"> children inside the dropdown."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <ren-date-range-picker> as the host so the upgrade lifecycle builds the trigger, dropdown, dual calendars, presets, and hidden inputs."
  - "The trigger is a real <button class=\"ren-date-range-trigger\"> with aria-haspopup=\"dialog\" and aria-expanded; do not replace it with a div."
  - "The dropdown root carries role=\"dialog\", aria-label, and popover=\"manual\"; presets render inside role=\"listbox\" with each preset as role=\"option\" + data-preset=\"<key>\"."
  - "Footer must contain .ren-date-range-summary (aria-live=\"polite\"), .ren-date-range-cancel, and .ren-date-range-apply — Apply is disabled until both endpoints are set."
  - "Use `name=\"…\"` on the host so the component injects <input type=\"hidden\" name=\"<name>-start\"> and `…-end` for form submission."
  - "Use placement=\"bottom\" by default; the host and .ren-date-range-dropdown mirror the preferred side to data-side."

forbiddenPatterns:
  - "Wiring presets to a single ren-calendar instead of using both calendarLeft/calendarRight — the host depends on having two ren-calendar children to call setRange on."
  - "Skipping the Apply button and writing directly to the trigger — Cancel/outside-click revert depends on a draft vs confirmed split."
  - "Hardcoding dropdown placement offsets or breakpoints — use placement/data-side for side preference, and keep the container-type: inline-size @container ren-date-range layout under 500px."
  - "Adding extra inline color tokens to .ren-date-range-preset[aria-selected=\"true\"] — the highlighted preset already styles via --color-accent-subtle / --color-accent."
  - "Calling showPopover() directly from outside — use the host's open() / close() / handleApply() / handleCancel() so draft/confirmed state stays consistent."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-calendar-bg, --ren-calendar-border, --ren-calendar-day-size, --ren-calendar-radius, --ren-calendar-range-bg, --ren-calendar-selected-bg, --ren-calendar-selected-color, --ren-calendar-today-bg, --ren-calendar-width."
    - "Semantic surface / text / accent tokens: --color-surface, --color-surface-raised, --color-border, --color-border-strong, --color-text, --color-text-secondary, --color-text-muted, --color-accent, --color-accent-hover, --color-accent-subtle, --color-on-accent, --color-fill, --color-fill-active, --color-danger, --color-success."
    - "Layout/motion tokens: --space-*, --radius-md, --radius-lg, --shadow-lg, --duration-enter, --ease-enter, --transition-tactile, --touch-min, --body-size, --label-size, --weight-semibold."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgba colors for any of the trigger, dropdown, presets, or actions — theme via the semantic and --ren-calendar-* tokens."
    - "Custom transition durations; reuse --duration-enter / --ease-enter so reduced-motion handling continues to apply."

accessibility:
  required:
    - "Trigger is a real <button type=\"button\"> with aria-haspopup=\"dialog\" and aria-expanded synced to the dropdown state."
    - "Dropdown is role=\"dialog\" with aria-label=\"Date range selection\" and focuses the first preset (or first calendar day) on open."
    - "Preset listbox uses role=\"listbox\" with each .ren-date-range-preset as role=\"option\"; the active preset sets aria-selected=\"true\"."
    - ".ren-date-range-summary uses aria-live=\"polite\" so changes to the draft range are announced."
    - "Escape closes via Cancel semantics (reverts draft to confirmed); outside-click does the same — do not silently apply."
    - "Apply button is disabled (real disabled attribute) until both draftStart and draftEnd are present; do not bypass via aria-disabled alone."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-date-range-picker/ren-date-range-picker.css">
<script type="module" src="rends/components/composites/ren-date-range-picker/ren-date-range-picker.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-date-range-picker name="stay" placement="bottom"><button class="ren-date-range-trigger" type="button" aria-haspopup="dialog" aria-expanded="false">Choose dates</button><div class="ren-date-range-dropdown" role="dialog" aria-label="Choose date range" popover="manual"><div class="ren-date-range-summary" aria-live="polite">No dates selected</div><button class="ren-date-range-cancel" type="button">Cancel</button><button class="ren-date-range-apply" type="button" disabled>Apply</button></div></ren-date-range-picker>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-calendar`
- `.ren-date-range-actions`
- `.ren-date-range-apply`
- `.ren-date-range-calendars`
- `.ren-date-range-cancel`
- `.ren-date-range-divider`
- `.ren-date-range-dropdown`
- `.ren-date-range-empty`
- `.ren-date-range-end`
- `.ren-date-range-error`
- `.ren-date-range-footer`
- `.ren-date-range-inner`
- `.ren-date-range-picker`
- `.ren-date-range-preset`
- `.ren-date-range-presets`
- `.ren-date-range-separator`
- `.ren-date-range-start`
- `.ren-date-range-success`
- `.ren-date-range-summary`
- `.ren-date-range-trigger`
- `.ren-date-range-value`

## States And Attributes

- `[aria-selected]`
- `[data-side]`
- `placement`
- `:active`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-calendar-bg`
- `--ren-calendar-border`
- `--ren-calendar-day-size`
- `--ren-calendar-radius`
- `--ren-calendar-range-bg`
- `--ren-calendar-selected-bg`
- `--ren-calendar-selected-color`
- `--ren-calendar-today-bg`
- `--ren-calendar-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-date-range-picker/ren-date-range-picker.css`
- `components/composites/ren-date-range-picker/ren-date-range-picker.js`
- `docs/components/ren-date-range-picker.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-dialog
type: contract
name: ren-dialog component.md
path: components/composites/ren-dialog/component.md
data: {"kind":"composite"}
body:
# ren-dialog

Modal dialog composite built on the native `<dialog>` element with focus
trapping, scroll locking, animated open/close, and an alert-mode for
critical confirmations.

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `<ren-dialog>` UI.

## Purpose

Owns the modal-overlay UX of a `<dialog showModal>` plus keyboard / a11y
behavior: focus trap inside the dialog, restore focus on close, dismiss
on Escape (unless `no-escape`), close on backdrop click (unless `alert`),
and animated open/close that respects `prefers-reduced-motion`. Supports
size variants and a mobile sheet adaptation.

## Use When

- The UI requires the user's full attention before continuing — confirms,
  destructive flows, multi-step settings panes.
- The interaction blocks the page underneath (background goes inert).
- A critical alert dialog should not be dismissable by accident
  (use `alert`).
- A multi-step settings or onboarding form should appear inline as a modal.

## Do Not Use When

- The disclosure is non-modal — use `ren-popover`, `ren-tooltip`,
  `ren-hover-card`.
- The disclosure is a side panel — use `ren-sheet`.
- The disclosure is a transient notification — use `ren-toast` /
  `ren-banner`.
- The control is a select / combobox / menu — use those composites.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The flow blocks the underlying page (modal)."
    - "You need focus trap, scroll lock, Escape-to-close, and backdrop dismiss."
    - "You need an alert variant that disables accidental dismissal."
    - "You want the native <dialog> semantics (form method=dialog, returnValue, etc.)."
  avoidWhen:
    - "The disclosure is non-modal (popover, tooltip)."
    - "The disclosure is a side panel — use ren-sheet."
    - "The notification should not steal focus — use ren-toast."

canonicalImports:
  css:
    - "rends/components/composites/ren-dialog/ren-dialog.css"
  js:
    - "rends/components/composites/ren-dialog/ren-dialog.js"
  notes:
    - "JS is required (focus trap, animation, scroll lock). The native <dialog> alone does not provide RenDS' UX guarantees."

requiredMarkup:
  - "<ren-dialog> wraps a real <dialog> element. Do not replace the inner <dialog> with a <div>."
  - "Provide a unique id on <ren-dialog> when triggers reference it via data-dialog-trigger."
  - "Always include .ren-dialog-title (rendered as a real heading) for the accessible name."
  - "Close affordances use [data-dialog-close]; do not call dialog.close() directly from arbitrary handlers."

forbiddenPatterns:
  - "<ren-dialog> wrapping a <div role=\"dialog\"> instead of a real <dialog>."
  - "Animating the backdrop manually with hardcoded rgba — use --ren-dialog-backdrop."
  - "Programmatic open via .show() when the user expects modal behavior — use .open() / .show() per native semantics."
  - "Putting form submit buttons outside <form method=\"dialog\"> when the dialog is a confirmation."

tokenPolicy:
  allowed:
    - "Component tokens: every --ren-dialog-* listed in Public Token API."
    - "Semantic tokens for content inside the dialog (--color-text, --color-text-muted, etc.)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code."
    - "Hardcoded backdrop colors; use --ren-dialog-backdrop."

accessibility:
  required:
    - "Use a real <dialog> inside <ren-dialog> so the user agent provides modality semantics."
    - ".ren-dialog-title supplies the accessible label when no aria-label is set."
    - "Modal dialogs trap focus and inert the rest of the page automatically; do not break the trap."
    - "Escape closes the dialog unless no-escape is set."
    - "Backdrop click closes the dialog unless alert is set."
    - "Restore focus to the trigger after close (handled by the component)."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-dialog/ren-dialog.css">
<script type="module" src="rends/components/composites/ren-dialog/ren-dialog.js"></script>
```

## Canonical Markup

Confirmation dialog with trigger:

```html
<button class="ren-btn" data-dialog-trigger="confirm-delete">Delete project</button>

<ren-dialog id="confirm-delete" alert>
  <dialog>
    <div class="ren-dialog-header">
      <h2 class="ren-dialog-title">Delete project</h2>
      <button class="ren-dialog-close" data-dialog-close aria-label="Close"></button>
    </div>
    <div class="ren-dialog-body">
      <p>This action cannot be undone.</p>
    </div>
    <div class="ren-dialog-footer">
      <button class="ren-btn ren-btn-secondary" data-dialog-close>Cancel</button>
      <button class="ren-btn ren-btn-danger" data-dialog-close="delete">Delete</button>
    </div>
  </dialog>
</ren-dialog>
```

Native command API (when supported):

```html
<button commandfor="settings" command="show-modal">Open settings</button>
<ren-dialog id="settings">
  <dialog>...</dialog>
</ren-dialog>
```

Programmatic API:

```js
const dlg = document.querySelector('#settings');
dlg.show();              // open
dlg.close('saved');      // close with returnValue
dlg.addEventListener('ren-close', (e) => console.log(e.detail.returnValue));
```

## Attributes, Events, and API

- Host attributes: `open`, `alert`, `size="sm|md|lg|xl|full"`, `no-escape`.
- Trigger attributes: `[data-dialog-trigger="<id>"]`, native
  `commandfor` + `command="show-modal"` where supported.
- Close attributes: `[data-dialog-close]`. Optional value becomes the
  dialog's `returnValue`.
- Methods: `show()`, `open()`, `close(returnValue)`.
- Getters: `isOpen`, `dialog`.
- Events: `ren-open`, `ren-close` (with `detail.returnValue`).

## Variants

| Class                  | Role                                   |
|------------------------|----------------------------------------|
| `.ren-dialog`          | Default surface (applied internally).  |
| `.ren-dialog-sm`       | Small width.                           |
| `.ren-dialog-md`       | Medium (default size).                 |
| `.ren-dialog-lg`       | Large width.                           |
| `.ren-dialog-xl`       | Extra-large.                           |
| `.ren-dialog-full`     | Full-screen on desktop.                |
| `.ren-alert-dialog`    | Critical confirmation styling.         |
| `.ren-dialog-header`   | Header slot.                           |
| `.ren-dialog-title`    | Heading slot (renders as `<h*>`).      |
| `.ren-dialog-description` | Subheading / description slot.      |
| `.ren-dialog-body`     | Scrollable body slot.                  |
| `.ren-dialog-footer`   | Action footer.                         |
| `.ren-dialog-close`    | Close button slot.                     |

## States

| Selector / attr             | Meaning                                  |
|-----------------------------|------------------------------------------|
| `[open]`                    | Native `<dialog>` open state.            |
| `[data-mobile-sheet]`       | Mobile adaptation as a bottom sheet.     |
| `[data-dialog-trigger]`     | Marks an opener for a target dialog.     |
| `[data-dialog-close]`       | Marks a close affordance.                |

## Public Token API

- `--ren-dialog-bg`
- `--ren-dialog-border-color`
- `--ren-dialog-radius`
- `--ren-dialog-padding`
- `--ren-dialog-gap`
- `--ren-dialog-width`
- `--ren-dialog-shadow`
- `--ren-dialog-backdrop`
- `--ren-dialog-duration`
- `--ren-dialog-easing`

## Accessibility Contract

- A real `<dialog>` element supplies modality semantics; never substitute a
  `<div role="dialog">`.
- `.ren-dialog-title` provides the accessible name; if you omit it, set
  `aria-label` on `<ren-dialog>`.
- The component traps focus inside the dialog and inerts background content.
- Escape closes the dialog unless `no-escape` is set.
- Backdrop click closes the dialog unless `alert` is set.
- After close, focus returns to the original trigger.

## Anti-Patterns

- ❌ `<ren-dialog><div role="dialog">…</div></ren-dialog>` — must be a real
  `<dialog>` inside.
- ❌ Calling `dialog.show()` when modal behavior is needed — use `.open()` /
  the native `.showModal()` flow the component provides.
- ❌ Custom backdrops via `position: fixed; background: rgba(0,0,0,.5)` —
  use `--ren-dialog-backdrop`.
- ❌ Skipping `.ren-dialog-title` and not providing `aria-label`.

## Related Files

- `components/composites/ren-dialog/ren-dialog.css`
- `components/composites/ren-dialog/ren-dialog.js`
- `docs/components/ren-dialog.html`
- `ren-design.md`
- `tokens/tokens.md`

## Test Expectations

- Run component / docs a11y coverage on focus trap, Escape, backdrop click.
- Run `npm run lint` after token / selector changes.
- Manually verify reduced-motion: open / close should still work but
  without animation.


id: contract:composite:ren-dropzone
type: contract
name: ren-dropzone component.md
path: components/composites/ren-dropzone/component.md
data: {"kind":"composite"}
body:
# ren-dropzone Component Contract

File drop and upload affordance with keyboard fallback.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-dropzone` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-dropzone` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Dropzone composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User must upload files via drag-and-drop OR click-to-browse with a single clear target."
    - "You want a dashed-border surface that highlights on dragover and lists uploaded files inline."
    - "Drop must also work for keyboard / pointer users by clicking through to a native <input type=\"file\">."
    - "You need a custom event (ren-files-added) carrying File[] instead of wiring the change event yourself."
    - "Showing accepted types / max size hints next to the icon is part of the flow."
  avoidWhen:
    - "The flow is a single hidden file picker triggered by a button — use a plain <input type=\"file\"> + ren-button label."
    - "Files come from a remote URL / clipboard image only — dropzone is for OS file drops."
    - "You need a full upload manager with progress bars, chunked upload, or retries — wrap this dropzone with your own upload UI."
    - "The drop target is a generic editor surface where the file IS the document body (rich-text editor, canvas)."

canonicalImports:
  css:
    - "rends/components/composites/ren-dropzone/ren-dropzone.css"
  js:
    - "rends/components/composites/ren-dropzone/ren-dropzone.js"
  notes:
    - "JS is exposed as initDropZone(el) — call it explicitly on each .ren-dropzone, there is no auto-upgrade custom element."
    - "Without the JS, the CSS still renders a styled file input target, but you lose the dragover state, the dispatched event, and the file array shape."

requiredMarkup:
  - "Root is <div class=\"ren-dropzone\"> (or with .ren-dropzone-compact) and contains a single <input type=\"file\" class=\"ren-dropzone-input\"> covering the surface."
  - "The file input MUST be a real <input type=\"file\"> with absolute positioning; do not replace it with a custom button — keyboard users rely on the native click."
  - "Content sits in .ren-dropzone-content with .ren-dropzone-icon, .ren-dropzone-title, .ren-dropzone-description as siblings inside it."
  - "Uploaded files render as <div class=\"ren-dropzone-files\"> > .ren-dropzone-file rows with .ren-dropzone-file-name / -size / -remove inside each."
  - "Disabled state must set aria-disabled=\"true\" on the wrapper AND disabled on the inner <input> — :has(input:disabled) styles depend on both."

forbiddenPatterns:
  - "Removing <input type=\"file\"> and relying on dragover events alone — there is no keyboard fallback without the native input."
  - "Setting display: none on .ren-dropzone-input — the CSS expects it to overlay the dropzone for click-to-browse."
  - "Toggling drag highlight via inline styles — let the JS set/unset the [data-dragover] attribute so the state and the icon color react together."
  - "Custom error tints via hardcoded red — use the [data-error] attribute which switches the border to --color-danger."
  - "Putting the .ren-dropzone-content above the input in stacking order with pointer-events: auto — content uses pointer-events: none so clicks reach the file input."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-dropzone-bg, --ren-dropzone-border-color, --ren-dropzone-border-style, --ren-dropzone-border-width, --ren-dropzone-hover-bg, --ren-dropzone-hover-border, --ren-dropzone-padding, --ren-dropzone-radius."
    - "Semantic tokens used by the file list and icon: --color-surface-sunken, --color-surface-raised, --color-border, --color-border-strong, --color-fill, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-danger, --color-focus-ring."
    - "Layout / motion / type tokens: --space-*, --radius-md, --radius-lg, --radius-sm, --radius-full, --stroke-1, --text-xl, --text-base, --text-sm, --body-size, --caption-size, --weight-medium, --ring-width, --ring-offset-width, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer styles."
    - "Hardcoded hex / rgb() values for border, dragover background, or remove-button color — theme via the --color-* tokens."
    - "Custom focus rings on the wrapper that bypass --color-focus-ring / --ring-width / --ring-offset-width."

accessibility:
  required:
    - "Dropzone exposes :focus-within outline (--color-focus-ring) sourced from the inner native <input type=\"file\"> — do not remove the outline."
    - "Provide a visible label inside .ren-dropzone-title plus a description in .ren-dropzone-description so screen readers see the purpose, not just the icon."
    - "Disabled state requires aria-disabled=\"true\" AND the native disabled attribute; pointer-events: none alone leaves the input focusable."
    - "Remove buttons inside .ren-dropzone-file must include accessible names (aria-label=\"Remove <file name>\") because the visible glyph alone is decorative."
    - "Drag visual state ([data-dragover]) must not be the only signal — pair it with text feedback when validation fails ([data-error])."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-dropzone/ren-dropzone.css">
<script type="module" src="rends/components/composites/ren-dropzone/ren-dropzone.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-dropzone"><input class="ren-dropzone-input" type="file" multiple><div class="ren-dropzone-content"><span class="ren-dropzone-icon" aria-hidden="true">↑</span><strong class="ren-dropzone-title">Upload files</strong><span class="ren-dropzone-description">Drag files here or browse</span></div></div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-dropzone`
- `.ren-dropzone-compact`
- `.ren-dropzone-content`
- `.ren-dropzone-description`
- `.ren-dropzone-file`
- `.ren-dropzone-file-name`
- `.ren-dropzone-file-remove`
- `.ren-dropzone-file-size`
- `.ren-dropzone-files`
- `.ren-dropzone-icon`
- `.ren-dropzone-input`
- `.ren-dropzone-title`

## States And Attributes

- `[aria-disabled]`
- `[data-dragover]`
- `[data-error]`
- `:disabled`
- `:hover`

## Public Token API

- `--ren-dropzone-bg`
- `--ren-dropzone-border-color`
- `--ren-dropzone-border-style`
- `--ren-dropzone-border-width`
- `--ren-dropzone-hover-bg`
- `--ren-dropzone-hover-border`
- `--ren-dropzone-padding`
- `--ren-dropzone-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-dropzone/ren-dropzone.css`
- `components/composites/ren-dropzone/ren-dropzone.js`
- `docs/components/ren-dropzone.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-hover-card
type: contract
name: ren-hover-card component.md
path: components/composites/ren-hover-card/component.md
data: {"kind":"composite"}
body:
# ren-hover-card Component Contract

Non-blocking preview surface shown from hover/focus intent.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-hover-card` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-hover-card` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Hover Card composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "Hover or keyboard-focus over a trigger should reveal a richer-than-tooltip preview (header + body + footer)."
    - "The preview is informational and non-essential — user can ignore it without losing access to the action."
    - "Show/hide should respect intent: 200ms show delay, 300ms hide grace area so the cursor can move into the card."
    - "Need anchor positioning via position-area + viewport flip via CSS position-try-fallbacks (no JS positioning)."
    - "Trigger keyboard accessibility via focusin/focusout, not just :hover."
  avoidWhen:
    - "The trigger is a primary action and the disclosure is required for the task — use ren-popover (click-driven) or ren-dialog."
    - "You only need a short text annotation — use ren-tooltip (smaller surface, role=\"tooltip\")."
    - "The disclosure must be operable on touch devices without a hover state — touch has no hover; use ren-popover."
    - "Content includes form inputs, focusable controls beyond simple links — ren-popover gives proper dialog semantics."

canonicalImports:
  css:
    - "rends/components/composites/ren-hover-card/ren-hover-card.css"
  js:
    - "rends/components/composites/ren-hover-card/ren-hover-card.js"
  notes:
    - "JS is required: it adds anchor-name/position-anchor inline styles, wires mouseenter/leave + focusin/out, and toggles the native Popover API."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Place the trigger element immediately before <ren-hover-card>, OR set data-hover-trigger=\"<selector>\" on the host to point at a remote trigger."
  - "Inside <ren-hover-card> include a real element with class=\"ren-hover-card\" (the JS will add popover=\"manual\" and the .ren-hover-card class if missing)."
  - "Use .ren-hover-card-header / -body / -footer as the three layout slots; headers should contain a real <h3> or <h4> for the accessible heading."
  - "The trigger receives aria-haspopup=\"tooltip\" and aria-expanded automatically — do not override these in markup."
  - "Show/hide delays come from show-delay / hide-delay attributes (milliseconds) on the host; do not throttle via inline scripts."

forbiddenPatterns:
  - "Using ren-hover-card for click-driven disclosure — there is no click handler, only mouseenter/focusin."
  - "Putting interactive form inputs inside .ren-hover-card-body — the card closes on mouseleave/focusout, so focusing an input would dismiss it."
  - "Manually setting popover=\"auto\" on the inner card — the JS uses popover=\"manual\" so the open/close lifecycle stays in the component's hands."
  - "Overriding the ::before arrow with custom shapes — keep the inherited background/border so it auto-themes."
  - "Hardcoding width via inline styles or non-token values — use .ren-hover-card-sm / -lg or override --color-surface / --shadow-xl through semantic tokens."

tokenPolicy:
  allowed:
    - "Component-level positioning anchor token: --ren-hover-card-anchor (and the internal --ren-hover-card-anchor anchor-name)."
    - "Semantic tokens that style the card chrome and content: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-strong, --shadow-xl, --radius-lg, --radius-md."
    - "Layout / type / motion tokens: --space-* (0-25, 0-5, 1, 2, 3, 4, 5), --font-size-body, --font-size-body-sm, --font-size-xs, --duration-enter, --ease-enter, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded box-shadow / hex colors on the card surface — pipe through --shadow-xl and --color-surface / --color-border."
    - "Custom transition durations; reuse --duration-enter / --ease-enter to honour reduced-motion."

accessibility:
  required:
    - "Trigger must be a focusable element so focusin/focusout reveal the card via keyboard, not only hover."
    - "Trigger carries aria-haspopup=\"tooltip\" and aria-expanded that flips true/false in sync with show()/hide()."
    - "The card uses the native Popover API (popover=\"manual\") so it inerts other content correctly when shown."
    - "Reduced motion disables the entrance animation and hides the decorative arrow; do not override the @media (prefers-reduced-motion: reduce) rules."
    - "On viewports under 640px the card stretches to viewport width — do not lock min-width that breaks this responsive behavior."
    - "Hover cards must never be the only path to an action; provide a click-driven affordance (ren-popover / ren-link) for touch and AT users."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-hover-card/ren-hover-card.css">
<script type="module" src="rends/components/composites/ren-hover-card/ren-hover-card.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<button id="profile-trigger" type="button">Jane Doe</button><ren-hover-card data-hover-trigger="#profile-trigger"><div class="ren-hover-card"><div class="ren-hover-card-header"><h3>Jane Doe</h3></div><div class="ren-hover-card-body">Product designer</div></div></ren-hover-card>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-hover-card`
- `.ren-hover-card-body`
- `.ren-hover-card-disabled`
- `.ren-hover-card-footer`
- `.ren-hover-card-header`
- `.ren-hover-card-lg`
- `.ren-hover-card-loading`
- `.ren-hover-card-sm`
- `.ren-hover-card-trigger`

## States And Attributes

- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-hover-card-anchor`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-hover-card/ren-hover-card.css`
- `components/composites/ren-hover-card/ren-hover-card.js`
- `docs/components/ren-hover-card.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-menu
type: contract
name: ren-menu component.md
path: components/composites/ren-menu/component.md
data: {"kind":"composite"}
body:
# ren-menu Component Contract

Menu/listbox-style command surface with keyboard navigation.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-menu` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-menu` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Menu composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "A trigger button needs to open a list of imperative commands (Edit, Duplicate, Delete) with role=\"menu\"."
    - "You need keyboard navigation (Arrow keys, Home/End, typeahead) plus click-outside / Escape dismissal."
    - "Items must support menuitem, menuitemcheckbox, menuitemradio, separators, group labels, danger styling, and right-aligned shortcut text."
    - "Right-click context menus reuse the same chrome via the colocated ren-context-menu module at pointer coordinates."
    - "Positioning needs viewport-aware placement (bottom-start / bottom-end / top-start / top-end, with right/left also accepted) with native Popover API support."
  avoidWhen:
    - "User is selecting a single value to commit into a form field — use ren-select."
    - "User is filtering a combobox of suggestions — use ren-combobox."
    - "The disclosure is non-list (rich content, header/body/footer) — use ren-popover or ren-hover-card."
    - "Items are persistent navigation across the app — use ren-sidebar / ren-nav."

canonicalImports:
  css:
    - "rends/components/composites/ren-menu/ren-menu.css"
  js:
    - "rends/components/composites/ren-menu/ren-menu.js"
  notes:
    - "JS depends on utils/keyboard-nav.js and utils/dismissable.js; its compatibility export delegates ren-context-menu registration to the colocated context-menu module."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Place a trigger element with data-menu-trigger (or referenced via trigger-id=\"<id>\" on the host) immediately before <ren-menu>."
  - "Each interactive item is a real <button class=\"ren-menu-item\"> with role=\"menuitem\" (the component sets role=\"menuitemcheckbox\" / \"menuitemradio\" when those classes are present)."
  - "Visual separators use <div class=\"ren-menu-separator\"> and group titles use <div class=\"ren-menu-label\">; do not use <hr> or <h*> for these slots."
  - "Disabled items set aria-disabled=\"true\"; the component skips them in keyboard navigation and selection."
  - "Set data-value=\"…\" on items so the dispatched ren-menu-select event carries that value in detail.value (falls back to textContent)."
  - "Use placement=\"bottom-start\" by default; the host mirrors the resolved side/alignment to data-side=\"top|right|bottom|left\" and data-align=\"start|end\"."

forbiddenPatterns:
  - "Using <a href> for menu items that perform JS actions — use <button> and listen to ren-menu-select."
  - "Replacing the trigger with a div that toggles via inline click — the trigger must receive aria-haspopup=\"menu\" + aria-controls + aria-expanded from the component."
  - "Manually positioning the menu with custom left/top — let positionMenu() handle viewport collision (or use placement=\"bottom-end\" etc)."
  - "Toggling visibility with display: none — the menu relies on [popover] / .ren-open and animates via the [data-closing] state."
  - "Putting non-menu rich content (forms, multi-line text blocks) directly inside <ren-menu> — wrap such content in ren-popover instead."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-menu-bg, --ren-menu-border, --ren-menu-danger-color, --ren-menu-duration, --ren-menu-easing, --ren-menu-item-height, --ren-menu-item-hover-bg, --ren-menu-item-padding, --ren-menu-item-radius, --ren-menu-padding, --ren-menu-radius, --ren-menu-shadow, --ren-menu-width."
    - "Semantic tokens used inside items: --color-surface, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-danger, --color-danger-subtle, --color-accent."
    - "Layout / size / motion tokens: --space-* (1, 2, 3), --radius-sm, --radius-lg, --shadow-lg, --size-body, --size-caption, --size-icon-md, --z-dropdown, --duration-enter, --ease-enter, --duration-exit, --ease-exit, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / rgb() colors for hover, active, or danger states — pipe through --color-fill / --color-fill-hover / --color-danger / --color-danger-subtle."
    - "Custom animation timings; reuse --duration-enter / --duration-exit so reduced-motion handling continues to apply."

accessibility:
  required:
    - "<ren-menu> sets role=\"menu\" on itself and forces role=\"menuitem\" / \"menuitemcheckbox\" / \"menuitemradio\" on its children — do not override."
    - "Trigger receives aria-haspopup=\"menu\", aria-controls=\"<menu-id>\", and aria-expanded that flips with open()/close()."
    - "Keyboard contract: ArrowDown/ArrowUp on the trigger opens the menu; Arrow keys navigate vertically with loop; Home/End jump to first/last; typeahead matches printable characters; Enter/Space selects."
    - "Checkbox / radio items toggle aria-checked; radio items reset siblings within .ren-menu-radio-group (or the menu root) before activating themselves."
    - "Dismissable layer closes on Escape and outside click via the shared dismissable util; do not block its event listeners."
    - "Danger items rely on a paired text label (Delete, Remove) — color alone (--color-danger-subtle) is not sufficient."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-menu/ren-menu.css">
<script type="module" src="rends/components/composites/ren-menu/ren-menu.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<button id="actions-trigger" type="button" data-menu-trigger>Actions</button>
<ren-menu trigger-id="actions-trigger" placement="bottom-start"><button class="ren-menu-item" role="menuitem" data-value="edit">Edit</button><button class="ren-menu-item" role="menuitem" data-value="duplicate">Duplicate</button></ren-menu>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-menu`
- `.ren-menu-checkbox-item`
- `.ren-menu-group`
- `.ren-menu-item`
- `.ren-menu-item-danger`
- `.ren-menu-item-description`
- `.ren-menu-item-icon`
- `.ren-menu-item-shortcut`
- `.ren-menu-label`
- `.ren-menu-radio-item`
- `.ren-menu-separator`
- `.ren-open`

## States And Attributes

- `[aria-checked]`
- `[aria-disabled]`
- `[data-closing]`
- `[data-align]`
- `[data-highlighted]`
- `[data-menu-trigger]`
- `[data-side]`
- `placement`
- `:active`
- `:hover`

## Public Token API

- `--ren-menu-bg`
- `--ren-menu-border`
- `--ren-menu-danger-color`
- `--ren-menu-duration`
- `--ren-menu-easing`
- `--ren-menu-item-height`
- `--ren-menu-item-hover-bg`
- `--ren-menu-item-padding`
- `--ren-menu-item-radius`
- `--ren-menu-padding`
- `--ren-menu-radius`
- `--ren-menu-shadow`
- `--ren-menu-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-menu/ren-menu.css`
- `components/composites/ren-menu/ren-menu.js`
- `docs/components/ren-menu.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-number-field
type: contract
name: ren-number-field component.md
path: components/composites/ren-number-field/component.md
data: {"kind":"composite"}
body:
# ren-number-field Component Contract

Numeric input composite with increment/decrement affordances.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-number-field` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-number-field` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Number Field composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User needs a discrete numeric value with visible − / + stepper buttons (quantities, counts, ratings out of N)."
    - "Need long-press auto-repeat with acceleration on the stepper buttons (mouse and touch)."
    - "Need keyboard contract: ArrowUp/Down for ±step, Home/End for min/max, clamped to min/max/step bounds."
    - "Need data-invalid / data-valid states wired to the wrapper (single source of truth for the focus ring)."
    - "Need :has(:focus) to color the whole group, not just the inner input."
  avoidWhen:
    - "Free-form numeric typing without bounds — use a plain <input type=\"number\"> styled via ren-input."
    - "Continuous value selection with visible track — use ren-slider."
    - "Numeric value paired with a unit selector (10 px / 1 rem) — compose ren-input with a ren-select."
    - "Currency / locale-formatted amount with thousands separators — that needs a masked input, not a stepper."

canonicalImports:
  css:
    - "rends/components/composites/ren-number-field/ren-number-field.css"
  js:
    - "rends/components/composites/ren-number-field/ren-number-field.js"
  notes:
    - "Public Token API has no --ren-number-field-* tokens; theme through semantic input/fill tokens listed below."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <ren-number-field min=\"…\" max=\"…\" step=\"…\" value=\"…\"> as the host so attributes wire min/max/step on the inner input."
  - "Inner input is <input type=\"number\" class=\"ren-number-field-input\"> — keep type=\"number\" so mobile keyboards switch to digits."
  - "Decrement / increment must be real type=\"button\" steppers with class=\"ren-number-field-decrement\" / class=\"ren-number-field-increment\" and aria-label=\"Decrease\" / \"Increase\" (the component fills these if missing)."
  - "Validation state goes on the wrapper as [data-invalid] or [data-valid]; the inner focus ring color reads from --color-danger / --color-success."
  - "Size variants are .ren-number-field-sm and .ren-number-field-lg on the wrapper; do not size buttons or input independently."

forbiddenPatterns:
  - "Wrapping a <span role=\"button\"> instead of a real <button> for the steppers — long-press auto-repeat hooks depend on native button events."
  - "Showing the native spinner UI — the CSS already hides ::-webkit-inner-spin-button / ::-webkit-outer-spin-button and uses -moz-appearance: textfield."
  - "Bypassing the host's clamp() by writing this.value directly — call setValue(), increment(), or decrement() so min/max/step clamping runs and ren-change dispatches."
  - "Removing aria-label from the stepper buttons — the visible ± glyph alone has no accessible name."
  - "Custom outline on the inner input — focus styling is owned by the wrapper via :has(.ren-number-field-input:focus)."

tokenPolicy:
  allowed:
    - "Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text."
    - "Semantic neutral / state tokens: --color-text, --color-border, --color-fill-hover, --color-fill-active, --color-danger, --color-success."
    - "Layout / type / motion tokens: --space-1, --space-2, --space-3, --space-4, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --body-size, --text-sm, --text-lg, --font-mono, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / rgb() values; the current CSS still has two rgb() calls for focus-ring tints — do not propagate that pattern, theme via tokens."
    - "Inventing --ren-number-field-* custom properties not present in the source until they ship in the Public Token API."

accessibility:
  required:
    - "Stepper buttons keep min-width / height = var(--touch-min) (44px) — do not shrink below this except inside .ren-number-field-sm for non-touch contexts."
    - "Each stepper has an aria-label (\"Decrease\" / \"Increase\"); the icon is decorative content."
    - "ArrowUp / ArrowDown on the input dispatch increment()/decrement(); Home / End jump to min / max; values are always clamped before dispatching ren-change."
    - "Disabled state sets disabled on the inner input AND both buttons so they are removed from the tab order — do not rely on opacity alone."
    - "data-invalid / data-valid changes border AND focus ring color, but always pair with text feedback near the field — color is not the only signal."
    - "Long-press auto-repeat must stop on pointerup/touchend AND mouseleave so dragging off the button cancels acceleration."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-number-field/ren-number-field.css">
<script type="module" src="rends/components/composites/ren-number-field/ren-number-field.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-number-field min="0" max="10" step="1" value="2"><button class="ren-number-field-decrement" type="button" aria-label="Decrease">−</button><input class="ren-number-field-input" type="number" aria-label="Quantity"><button class="ren-number-field-increment" type="button" aria-label="Increase">+</button></ren-number-field>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-number-field`
- `.ren-number-field-decrement`
- `.ren-number-field-increment`
- `.ren-number-field-input`
- `.ren-number-field-lg`
- `.ren-number-field-sm`

## States And Attributes

- `[data-invalid]`
- `[data-valid]`
- `:active`
- `:disabled`
- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-number-field/ren-number-field.css`
- `components/composites/ren-number-field/ren-number-field.js`
- `docs/components/ren-number-field.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-otp
type: contract
name: ren-otp component.md
path: components/composites/ren-otp/component.md
data: {"kind":"composite"}
body:
# ren-otp Component Contract

One-time-passcode segmented input composite.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-otp` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-otp` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Otp composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User must enter a short fixed-length code split across N single-character slots (SMS OTP, 2FA, email PIN)."
    - "Need auto-advance to the next slot, Backspace to previous slot, Arrow keys to navigate, Home/End to jump to first/last."
    - "Need clipboard paste to distribute the pasted string across consecutive slots and focus the next empty one."
    - "Need numeric vs alphanumeric filtering (inputmode=\"numeric\" or \"text\") and a per-slot data-filled state."
    - "Need explicit data-valid / data-invalid states on the wrapper plus a ren-complete event when all slots fill."
  avoidWhen:
    - "User pastes a long password / token in one go — use a single <input type=\"password\"> with autocomplete=\"one-time-code\" if it is a single field."
    - "Free-form numeric value with unit / decimals — use ren-number-field."
    - "Code length is variable or unknown — use a single text input."
    - "Code is alphanumeric AND case-sensitive in a way the slot UI cannot make obvious — fall back to a single masked input."

canonicalImports:
  css:
    - "rends/components/composites/ren-otp/ren-otp.css"
  js:
    - "rends/components/composites/ren-otp/ren-otp.js"
  notes:
    - "Public Token API has no --ren-otp-* tokens; theme through the semantic input tokens and size scale listed below."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <ren-otp length=\"6\" type=\"numeric\"> as the host element — slots are generated by JS, do not author them by hand."
  - "If you must author slots, each must be a real <input class=\"ren-otp-slot\" type=\"text\" maxlength=\"1\" inputmode=\"numeric|text\" aria-label=\"Code digit N of M\" data-index=\"N\">."
  - "Optional separators are <div class=\"ren-otp-separator\"> placed between slot groups; they are pointer-events: none and decorative."
  - "Size variants .ren-otp-sm and .ren-otp-lg go on the wrapper; do not size individual .ren-otp-slot."
  - "Validation state goes on the wrapper as [data-valid] or [data-invalid]; the per-slot [data-filled] is set automatically by the component."

forbiddenPatterns:
  - "Using a single <input maxlength=\"6\"> styled to look segmented — keyboard caret control and paste-distribute won't work."
  - "Removing maxlength=\"1\" / inputmode — without them mobile keyboards switch back to letters and the auto-advance breaks."
  - "Manually wiring focus on slot blur / change — let handleSlotInput / handleSlotKeydown manage focus transitions to keep Backspace and paste flows correct."
  - "Hardcoding the slot size — use --touch-min for the default, --size-sm via .ren-otp-sm, --size-lg via .ren-otp-lg."
  - "Mixing data-valid and data-invalid on the wrapper simultaneously — setValid() removes the opposite attribute by design."

tokenPolicy:
  allowed:
    - "Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-disabled-bg, --color-disabled-text."
    - "Semantic state / text tokens: --color-text, --color-text-muted, --color-border, --color-fill, --color-accent, --color-danger, --color-success."
    - "Layout / type / motion tokens: --space-0-5, --space-1, --space-2, --space-3, --stroke-1, --radius-sm, --radius-md, --radius-lg, --touch-min, --size-sm, --size-lg, --text-sm, --text-lg, --body-size, --font-mono, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / rgb() colors for focus rings or borders; the current CSS still uses rgb() for the danger / success ring tints — do not replicate, prefer tokens."
    - "Inventing --ren-otp-* custom properties not present in the source until they ship in the Public Token API."

accessibility:
  required:
    - "Slot dimensions default to var(--touch-min) (44px) for thumb accuracy; --size-sm only for non-touch surfaces."
    - "Each slot exposes aria-label=\"Code digit N of M\" so screen readers announce position; do not strip these labels."
    - "Backspace, ArrowLeft/Right, Home, End move focus across slots — keep the keydown handler intact when extending the component."
    - "Paste handler must run on every slot so users can paste from any slot, not only the first."
    - "data-valid and data-invalid are paired with text feedback elsewhere on the form (do not communicate validation through color alone)."
    - ":focus-visible draws --color-accent at 2px outline; do not remove it without restoring an equivalent ring."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-otp/ren-otp.css">
<script type="module" src="rends/components/composites/ren-otp/ren-otp.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-otp length="6" type="numeric" aria-label="Verification code"></ren-otp>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-otp`
- `.ren-otp-lg`
- `.ren-otp-separator`
- `.ren-otp-slot`
- `.ren-otp-sm`

## States And Attributes

- `[data-filled]`
- `[data-invalid]`
- `[data-valid]`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-otp/ren-otp.css`
- `components/composites/ren-otp/ren-otp.js`
- `docs/components/ren-otp.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-popover
type: contract
name: ren-popover component.md
path: components/composites/ren-popover/component.md
data: {"kind":"composite"}
body:
# ren-popover Component Contract

Non-modal floating surface for contextual controls or content.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-popover` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-popover` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Popover composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "Click on a trigger should reveal a small contextual surface (filters, mini-form, info card) anchored to that trigger."
    - "You need anchor-positioned placement (top/right/bottom/left) via position-area with automatic viewport flip via position-try-fallbacks."
    - "Need click-outside + Escape dismissal and a visible arrow caret that re-rotates per [data-side]."
    - "Need progressive enhancement: anchor positioning in modern browsers, JS computePosition() fallback elsewhere."
    - "Need header / body / footer slots inside the popover surface, with native role=\"dialog\" + aria-modal=\"false\"."
  avoidWhen:
    - "The disclosure blocks the page and demands the user's attention — use ren-dialog (modal)."
    - "Disclosure is reveal-on-hover-intent for non-essential preview content — use ren-hover-card."
    - "Disclosure is a short text annotation only — use ren-tooltip."
    - "Disclosure is a list of commands (menu) — use ren-menu / ren-context-menu."
    - "Disclosure is a side sheet — use ren-sheet."

canonicalImports:
  css:
    - "rends/components/composites/ren-popover/ren-popover.css"
  js:
    - "rends/components/composites/ren-popover/ren-popover.js"
  notes:
    - "JS attaches click + Escape + outside-click handlers and computes JS positioning only when CSS anchor positioning is unsupported."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <ren-popover placement=\"bottom\" offset=\"8\"> as the host so it adds .ren-popover, role=\"dialog\", and popover=\"manual\" automatically."
  - "The trigger is referenced via trigger-id=\"<id>\", or by a child / previous-sibling with [data-popover-trigger]; the component picks them in that order."
  - "Inside the popover, group content into .ren-popover-header / .ren-popover-body / .ren-popover-footer; a .ren-popover-arrow div is injected if missing."
  - "Triggers should be real interactive elements (<button> or <a>) so click + keyboard activation work; the component does not click-style arbitrary nodes."
  - "When using multiple popovers in the same view, set unique trigger-id values so the click handlers do not cross-wire."

forbiddenPatterns:
  - "Manually wiring trigger.addEventListener('click', popover.show) — the host already attaches its click + dismissable listeners."
  - "Calling showPopover() directly while bypassing open() — aria-modal, data-state, and ren-open event won't fire."
  - "Hardcoding inline left/top values — let CSS position-anchor + position-area + position-try-fallbacks handle placement and flips (or the JS computePosition fallback)."
  - "Removing the .ren-popover-arrow element — the [data-side] rules rotate it per side; without it the visual anchor cue disappears."
  - "Using ren-popover purely as a click-bound tooltip — accessibility expectations differ; use ren-tooltip for short, non-interactive text."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-popover-bg, --ren-popover-border-color, --ren-popover-duration, --ren-popover-easing, --ren-popover-padding, --ren-popover-radius, --ren-popover-shadow, --ren-popover-width."
    - "Semantic tokens used inside content: --color-surface, --color-border, --color-text-primary, --color-text-secondary, --shadow-lg, --radius-lg."
    - "Layout / type / motion tokens: --space-2, --space-3, --space-4, --size-body-sm, --z-popover, --duration-enter, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded backdrop / surface colors via inline styles or rgba()."
    - "Custom animation timings; reuse --duration-enter / --ease-enter (or --ren-popover-duration / --ren-popover-easing) so reduced-motion overrides apply."

accessibility:
  required:
    - "<ren-popover> sets role=\"dialog\" with aria-modal=\"false\" (non-modal by design). Do not set aria-modal=\"true\" or apply focus trapping."
    - "Trigger must be a focusable, real interactive element; click + Enter/Space activation come from native button semantics."
    - "Escape closes the popover via the document keydown handler; outside-click closes via the document click handler — do not stopPropagation on container click outside these targets."
    - "When CSS anchor positioning is unsupported the host applies left/top to the popover element; do not overlay your own positioning style or the flip logic breaks."
    - "Reduced motion strips the transform animation (opacity-only); keep the @media (prefers-reduced-motion: reduce) block intact."
    - "Avoid putting form submit buttons that affect the page outside the popover surface — keep the interaction self-contained within the dialog role."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-popover/ren-popover.css">
<script type="module" src="rends/components/composites/ren-popover/ren-popover.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<button id="account-trigger" type="button" data-popover-trigger>Account</button>
<ren-popover trigger-id="account-trigger" placement="bottom">
  <div class="ren-popover-header"><strong>Account</strong></div>
  <div class="ren-popover-body">Signed in as Jane</div>
</ren-popover>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-open`
- `.ren-popover`
- `.ren-popover-arrow`
- `.ren-popover-body`
- `.ren-popover-footer`
- `.ren-popover-header`
- `.ren-popover-trigger`

## States And Attributes

- `[data-popover-trigger]`
- `[data-side]`

## Public Token API

- `--ren-popover-bg`
- `--ren-popover-border-color`
- `--ren-popover-duration`
- `--ren-popover-easing`
- `--ren-popover-padding`
- `--ren-popover-radius`
- `--ren-popover-shadow`
- `--ren-popover-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-popover/ren-popover.css`
- `components/composites/ren-popover/ren-popover.js`
- `docs/components/ren-popover.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-scroll-area
type: contract
name: ren-scroll-area component.md
path: components/composites/ren-scroll-area/component.md
data: {"kind":"composite"}
body:
# ren-scroll-area Component Contract

Scrollable region styling that preserves native scrolling.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-scroll-area` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-scroll-area` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Scroll Area composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "A bounded region needs themed native scrollbars (Firefox scrollbar-color + WebKit ::-webkit-scrollbar)."
    - "You want consistent scrollbar styling across light/dark themes without injecting JS or shadow DOM."
    - "Need preset max-height utilities (.ren-scroll-area-sm/-md/-lg/-xl/-full) and direction variants (.-x, .-y)."
    - "Need fade-edge masks (.-fade, .-fade-x) to hint at overflow without resorting to JS observers."
    - "Need an \"auto\" variant where the scrollbar only appears on hover / focus-within."
  avoidWhen:
    - "You need virtualization, restore-scroll-position, or scroll-into-view APIs — those need bespoke JS, not this CSS contract."
    - "You need a custom-drawn track / thumb (e.g. mini-map, position indicator) — native scrollbar styling cannot do this."
    - "The container should never scroll (clip overflow) — use overflow: clip / hidden directly."
    - "The content scrolls horizontally only and needs snap behavior — use a dedicated carousel/scroll-snap pattern."

canonicalImports:
  css:
    - "rends/components/composites/ren-scroll-area/ren-scroll-area.css"
  notes:
    - "CSS-only component: no JavaScript file is colocated. Do not introduce JS unless the source component grows that responsibility."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Apply class=\"ren-scroll-area\" to a real block element that owns overflow (commonly <div>, <section>, <main>)."
  - "Constrain height/width explicitly — either via .ren-scroll-area-sm/-md/-lg/-xl/-full, an inline --scroll-max custom property, or a parent flex/grid track."
  - "Pair direction with intent: .-x for horizontal-only, .-y for vertical-only; omit for auto in both axes."
  - "When using .-fade or .-fade-x the masking ::before/::after read from --color-surface; ensure the scroll area sits on a surface that matches that token."
  - "Children that should be focusable for keyboard scrolling need tabindex=\"0\" on the .ren-scroll-area itself when it has no focusable descendants."

forbiddenPatterns:
  - "Overriding overflow with overflow: hidden — that defeats the entire component (no scrollbar, no fade mask alignment)."
  - "Wrapping a <ren-scroll-area> custom element instead of the .ren-scroll-area class — no custom element exists for this composite."
  - "Stacking position: absolute children that escape the scroll context — the fade overlays (::before/::after at z-index: 10) assume in-flow content underneath."
  - "Hardcoding scrollbar-color or ::-webkit-scrollbar-thumb hex values — theme via --color-fill-active / --color-fill-hover so dark mode adapts."
  - "Setting --scroll-max on the parent expecting cascade — the rule is .ren-scroll-area[--scroll-max] { max-height: var(--scroll-max) } so the variable must be authored on the same element."

tokenPolicy:
  allowed:
    - "Semantic tokens used by the scrollbar and fade masks: --color-fill-active, --color-fill-hover, --color-surface."
    - "Layout / shape / motion tokens: --radius-full, --duration-enter, --ease-enter, --transition-tactile."
    - "Authoring custom max-height via the --scroll-max custom property on the .ren-scroll-area element."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / rgb() values for scrollbar thumb or fade-mask gradients."
    - "Inventing --ren-scroll-area-* tokens — none are part of the Public Token API today."

accessibility:
  required:
    - "If the scrollable region contains no focusable child, set tabindex=\"0\" on the .ren-scroll-area so keyboard users can scroll it with Arrow / PageUp / PageDown."
    - "Provide an accessible name (aria-label / aria-labelledby) when the region is a discrete content landmark; e.g. <section class=\"ren-scroll-area\" aria-label=\"Logs\">."
    - "Honor prefers-reduced-motion: the component disables scroll-behavior: smooth via the existing @media rule — do not re-enable smooth scrolling under reduced-motion."
    - "Do not rely on the fade-edge gradients alone to signal more content; pair with a visible affordance (chevron, scroll-hint) when content overflow is decision-critical."
    - "Scrollbar contrast: --color-fill-active and --color-fill-hover must remain distinguishable from --color-surface in both themes; do not theme so the thumb disappears."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-scroll-area/ren-scroll-area.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<section class="ren-scroll-area ren-scroll-area-md" tabindex="0" aria-label="Activity log"><p>Activity entry</p></section>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-scroll-area`
- `.ren-scroll-area-full`
- `.ren-scroll-area-lg`
- `.ren-scroll-area-md`
- `.ren-scroll-area-sm`
- `.ren-scroll-area-xl`

## States And Attributes

- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/composites/ren-scroll-area/ren-scroll-area.css`
- `docs/components/ren-scroll-area.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-select
type: contract
name: ren-select component.md
path: components/composites/ren-select/component.md
data: {"kind":"composite"}
body:
# ren-select Component Contract

Custom select/listbox composite with trigger, options, and keyboard navigation.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-select` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-select` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Select composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a styled single- or multi-select with a button trigger and a listbox popup."
    - "You need full keyboard navigation (Arrow keys, Home/End, typeahead, Enter/Space, Escape)."
    - "You need ARIA combobox/listbox semantics with aria-expanded, aria-selected, and data-highlighted."
    - "You need optional grouping (.ren-select-group + .ren-select-label) or separators between options."
    - "You need multi-select chips (.ren-select-chips, .ren-select-chip) with removal affordances."
    - "You need hidden form-input integration for submission alongside custom dropdown UI."
    - "The dropdown needs a preferred placement via placement=\"top|right|bottom|left\" while preserving viewport flip fallback."
  avoidWhen:
    - "A bare <select> styled via base/primitive-zero would meet the requirements."
    - "The control is binary state — use ren-checkbox, ren-switch, or ren-toggle."
    - "The disclosure is a free-form menu of commands, not value selection — use ren-menu / ren-menubar."
    - "The disclosure is a navigation list — use ren-nav or ren-sidebar."

canonicalImports:
  css:
    - "rends/components/composites/ren-select/ren-select.css"
  js:
    - "rends/components/composites/ren-select/ren-select.js"
  notes:
    - "JS is required: it owns keyboard nav, dismissable popup, popover positioning, and the hidden form input."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "<ren-select> wraps a real <button data-select-trigger> and a [data-select-content] listbox container."
  - "Each option is a <div data-select-item data-value=\"...\"> inside .ren-select-content; rely on data-select-item, not arbitrary children."
  - "The trigger displays .ren-select-value when something is chosen and .ren-select-placeholder when empty — do not collapse them into one node."
  - "Set name on <ren-select> when the value must submit with a form; the component injects the hidden input automatically."
  - "With multiple, value is an ordered array and the component injects one same-name hidden input per selected value so FormData preserves repeated entries. Removable chips render as a sibling of the button trigger so interactive controls are never nested."
  - "Use .ren-select-group + .ren-select-label for grouped options and .ren-select-separator between groups; do not invent dividers."
  - "Use placement=\"bottom\" by default; the host and .ren-select-content mirror the resolved side to data-side and data-align."

forbiddenPatterns:
  - "Substituting a <div role=\"button\"> for the [data-select-trigger] <button> — the trigger must be a real button."
  - "Animating the dropdown manually with display: none / display: block — use the .ren-open class and the popover open state."
  - "Removing the focus ring on .ren-select-trigger:focus-visible without restoring an equivalent visible ring."
  - "Hardcoding option backgrounds via inline style — use --color-accent-subtle / --color-fill via the documented selectors."
  - "Putting interactive children (links, buttons) inside .ren-select-item; items must remain single-action options."

tokenPolicy:
  allowed:
    - "Semantic input tokens: --color-input-bg, --color-input-bg-hover, --color-input-border, --color-input-border-focus, --color-input-focus-ring, --color-input-placeholder, --color-input-disabled-bg, --color-input-disabled-text."
    - "Surface and content tokens: --color-surface, --color-fill, --color-accent, --color-accent-subtle, --color-text, --color-text-muted, --color-border."
    - "Shape / motion tokens: --radius-sm, --radius-md, --radius-lg, --space-*, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides."
    - "Hardcoded transition durations; use --duration-enter / --duration-exit / --duration-micro with their paired easings."

accessibility:
  required:
    - "Trigger exposes aria-expanded on open/close and aria-disabled when inert; native <button> Enter/Space activation must be preserved."
    - "Listbox container carries role=\"listbox\"; each option carries role=\"option\" and aria-selected reflects the selected value."
    - "Roving highlight on options uses [data-highlighted]; only one option may be highlighted at a time."
    - "Trigger meets the 44px touch target via min-height: var(--touch-min); .ren-select-sm is only acceptable in non-touch contexts."
    - "Escape closes the listbox and returns focus to the trigger; outside click dismisses without stealing focus elsewhere."
    - "Disabled state combines :disabled (or aria-disabled=\"true\") with pointer-events: none on items so click and keypress both no-op."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-select/ren-select.css">
<script type="module" src="rends/components/composites/ren-select/ren-select.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-select name="country"><button type="button" data-select-trigger><span class="ren-select-value"></span><span class="ren-select-placeholder">Choose a country</span></button><div class="ren-select-content" data-select-content role="listbox"><div data-select-item data-value="ar">Argentina</div><div data-select-item data-value="uy">Uruguay</div></div></ren-select>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-open`
- `.ren-select`
- `.ren-select-chip`
- `.ren-select-chip-remove`
- `.ren-select-chips`
- `.ren-select-content`
- `.ren-select-empty`
- `.ren-select-group`
- `.ren-select-icon`
- `.ren-select-item`
- `.ren-select-label`
- `.ren-select-lg`
- `.ren-select-placeholder`
- `.ren-select-separator`
- `.ren-select-sm`
- `.ren-select-trigger`
- `.ren-select-value`

## States And Attributes

- `[aria-disabled]`
- `[aria-expanded]`
- `[aria-selected]`
- `[data-highlighted]`
- `[data-align]`
- `[data-flipped]`
- `[data-select-content]`
- `[data-select-item]`
- `[data-select-trigger]`
- `[data-side]`
- `placement`
- `:disabled`
- `:focus-visible`
- `:hover`

## JavaScript API

- `value` / `setValue(value)` use a string (or `null`) in single-select mode.
- `value` / `setValue(values)` use an ordered array in `multiple` mode.
- Multiple selection accumulates and removes values without closing the
  listbox, renders `.ren-select-chip` entries, and submits repeated same-name
  hidden inputs.
- `aria-disabled="false"` remains operable; only native `disabled` or
  `aria-disabled="true"` disables an option.

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-select/ren-select.css`
- `components/composites/ren-select/ren-select.js`
- `docs/components/ren-select.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-sheet
type: contract
name: ren-sheet component.md
path: components/composites/ren-sheet/component.md
data: {"kind":"composite"}
body:
# ren-sheet Component Contract

Edge-attached dialog/surface for secondary workflows.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-sheet` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-sheet` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Sheet composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The disclosure is an edge-anchored panel (right / left / top / bottom) over the main app."
    - "You need the native <dialog> behavior — modal focus trap, ::backdrop, Escape to dismiss — on a side surface."
    - "You need a slide-in transition controlled by [data-side] with @starting-style fallback."
    - "You need swipe-to-dismiss on touch and outside-click / Escape dismissal."
    - "You need a mobile bottom sheet with a drag handle (.ren-sheet-handle)."
  avoidWhen:
    - "The disclosure should be centered and block the page — use ren-dialog."
    - "The panel is persistent app-shell navigation — use ren-sidebar."
    - "The disclosure is a small anchored popup — use ren-popover, ren-tooltip, or ren-menu."
    - "The notification is transient and must not steal focus — use ren-toast."

canonicalImports:
  css:
    - "rends/components/composites/ren-sheet/ren-sheet.css"
  js:
    - "rends/components/composites/ren-sheet/ren-sheet.js"
  notes:
    - "JS is required: it wraps the consumer's children in a real <dialog class=\"ren-sheet\"> and wires triggers, swipe, and lifecycle events."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "<ren-sheet id=\"...\" side=\"right|left|top|bottom\"> is the host; do not replace the inner <dialog> the component generates with a <div>."
  - "Header / body / footer slots use .ren-sheet-header, .ren-sheet-body, .ren-sheet-footer in that order so the body grows and the footer pins to the bottom."
  - "Provide .ren-sheet-title as a real heading; it supplies the accessible name when no aria-label is set on <ren-sheet>."
  - "Close affordances use [data-sheet-close]; triggers elsewhere on the page use [data-sheet-trigger=\"<id>\"]."
  - "Use size variants via the .-sm / .-md / .-lg / .-xl / .-full classes — do not inline width on the host."

forbiddenPatterns:
  - "<ren-sheet> rendered as a <div role=\"dialog\"> — the component must use the real <dialog> it generates."
  - "Calling .show() / .close() on the inner <dialog> directly; always use the <ren-sheet> .show() / .close() methods."
  - "Hardcoded backdrop colors (rgba(0,0,0,...)); use --color-overlay via the component's ::backdrop rule."
  - "Animating slide-in with custom @keyframes; use the documented [data-side] + @starting-style + --duration-enter / --ease-enter pipeline."
  - "Toggling visibility via display: none / display: block — the [open] attribute drives the @starting-style translate."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-sheet-backdrop, --ren-sheet-bg, --ren-sheet-border, --ren-sheet-duration, --ren-sheet-easing, --ren-sheet-padding, --ren-sheet-radius, --ren-sheet-shadow, --ren-sheet-width."
    - "Semantic surface tokens: --color-surface, --color-text, --color-text-muted, --color-border, --color-overlay, --color-fill, --color-fill-active, --color-fill-hover."
    - "Spacing / motion tokens: --space-*, --radius-full, --duration-enter, --duration-exit, --ease-enter, --ease-exit, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides — even for the box-shadow on the leading edge (use semantic tokens or override --ren-sheet-shadow)."
    - "Hardcoded transition durations; route through --duration-enter / --duration-exit and their paired easings."

accessibility:
  required:
    - "Modality is provided by the inner real <dialog> (focus trap + inert background); do not break the trap with manual focus moves."
    - ".ren-sheet-title supplies the accessible label; if omitted, set aria-label on <ren-sheet>."
    - "Escape closes the sheet (unless dismissible=\"false\"); the close button [data-sheet-close] has a visible aria-label."
    - "Restore focus to the original trigger element after close (handled by the component via the returnFocus reference)."
    - "Swipe-to-dismiss must coexist with keyboard dismissal; do not override touch-action on the dialog."
    - "Respect prefers-reduced-motion: the slide-in collapses to a plain fade automatically — do not reintroduce custom animations there."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-sheet/ren-sheet.css">
<script type="module" src="rends/components/composites/ren-sheet/ren-sheet.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<button type="button" data-sheet-trigger="filters">Open filters</button><ren-sheet id="filters" side="right"><header class="ren-sheet-header"><h2 class="ren-sheet-title">Filters</h2><button type="button" data-sheet-close aria-label="Close">×</button></header><div class="ren-sheet-body">Filter controls</div><footer class="ren-sheet-footer"><button type="button" data-sheet-close="apply">Apply</button></footer></ren-sheet>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-sheet`
- `.ren-sheet-body`
- `.ren-sheet-close`
- `.ren-sheet-description`
- `.ren-sheet-footer`
- `.ren-sheet-handle`
- `.ren-sheet-header`
- `.ren-sheet-title`

## States And Attributes

- `[data-sheet-close]`
- `[data-sheet-trigger]`
- `[data-side]`
- `:active`
- `:hover`

`close(returnValue)` and native `<form method="dialog">` closure emit exactly
one `ren-close` with `detail.returnValue`, while clearing host `open` and
restoring focus to the opener.

## Public Token API

- `--ren-sheet-backdrop`
- `--ren-sheet-bg`
- `--ren-sheet-border`
- `--ren-sheet-duration`
- `--ren-sheet-easing`
- `--ren-sheet-padding`
- `--ren-sheet-radius`
- `--ren-sheet-shadow`
- `--ren-sheet-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-sheet/ren-sheet.css`
- `components/composites/ren-sheet/ren-sheet.js`
- `docs/components/ren-sheet.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-slider
type: contract
name: ren-slider component.md
path: components/composites/ren-slider/component.md
data: {"kind":"composite"}
body:
# ren-slider Component Contract

Range input composite for numeric adjustment.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-slider` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-slider` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Slider composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User needs to pick a continuous numeric value within a known min/max range (volume, opacity, brightness, percentage)."
    - "You need a single-thumb slider or a dual-thumb range slider (.ren-slider-range) with two inputs."
    - "You need a visible value readout (.ren-slider-value via show-value attribute) and/or tick marks (.ren-slider-marks)."
    - "You need semantic color variants for status: .ren-slider-success / .ren-slider-warning / .ren-slider-danger."
    - "You need a size variant (.ren-slider-sm / .ren-slider-lg) or a vertical orientation (.ren-slider-vertical)."
    - "Native <input type=\"range\"> ARIA and keyboard support is sufficient (Arrow keys, Home/End, PageUp/PageDown)."
  avoidWhen:
    - "The choice is from a discrete enumerated list — use ren-select, ren-radio-group, or ren-toggle-group."
    - "The value is text, number-typed, or formatted (currency, date) — use ren-field with a typed input."
    - "The control toggles binary state — use ren-switch, ren-checkbox, or ren-toggle."
    - "You need rating stars — use ren-rating."

canonicalImports:
  css:
    - "rends/components/composites/ren-slider/ren-slider.css"
  js:
    - "rends/components/composites/ren-slider/ren-slider.js"
  notes:
    - "JS is required to compute the --value percentage CSS variable that paints the track fill; without it the gradient stays at 0%."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "<ren-slider> wraps a real <input type=\"range\"> with min / max / step / value set as attributes — never style a <div> as a slider."
  - "Pair the input with a real <label for=\"...\"> or set aria-label / aria-labelledby on the input for an accessible name."
  - "Use show-value attribute on <ren-slider> to opt into the .ren-slider-value readout; use label attribute to inject a .ren-slider-label."
  - "Dual-thumb range mode wraps two <input type=\"range\"> elements inside .ren-slider-range with a .ren-slider-track-input visual rail."
  - "In dual-thumb mode the value property and ren-slider-input / ren-slider-change detail.value are a two-number tuple in input order."
  - "Tick marks use .ren-slider-marks > .ren-slider-mark > (.ren-slider-mark-tick + .ren-slider-mark-label); preserve that structure."

forbiddenPatterns:
  - "Replacing <input type=\"range\"> with a <div> + JS drag — accessibility and form submission depend on the native input."
  - "Setting the track fill via inline background gradients; the JS updates --value and the CSS handles the gradient automatically."
  - "Hardcoding thumb colors (background: #fff / blue) on ::-webkit-slider-thumb or ::-moz-range-thumb — use --color-accent or the .ren-slider-success/warning/danger variants."
  - "Removing the focus-visible outline on ::-webkit-slider-thumb / ::-moz-range-thumb without restoring an equivalent visible focus indicator."
  - "Stripping aria-valuemin / aria-valuemax / aria-valuenow (native input provides these — do not block them via role overrides)."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-slider-duration, --ren-slider-easing, --ren-slider-fill-color, --ren-slider-thumb-bg, --ren-slider-thumb-border, --ren-slider-thumb-shadow, --ren-slider-thumb-size, --ren-slider-track-bg, --ren-slider-track-height, --ren-slider-track-radius."
    - "Semantic state tokens: --color-accent, --color-success, --color-warning, --color-danger, --color-fill-active, --color-fill-subtle, --color-text, --color-text-muted."
    - "Shape / motion tokens: --radius-full, --space-*, --shadow-sm, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides — including the white thumb border (keep it as a token override if you need to change it)."
    - "Hardcoded transition durations on the thumb hover/active scale; route through --transition-tactile."

accessibility:
  required:
    - "Use a real <input type=\"range\"> so native ARIA (role=slider, aria-valuemin/max/now) and keyboard (Arrow, Home/End, PageUp/Down) work."
    - "Visible focus ring on the thumb via outline: 2px solid var(--color-accent) with outline-offset; never set outline: none without an equivalent."
    - "Provide an accessible name via <label for> or aria-label; the .ren-slider-label class alone does not associate the label."
    - "Color variants must not be the only signal of state — accompany .ren-slider-danger with text or an icon for non-color cues."
    - "Disabled state uses :disabled on the input plus cursor: not-allowed; opacity changes alone are not sufficient on their own for state."
    - "Touch / drag interactions must not block keyboard nudges — both must drive the same change/input events the component dispatches."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-slider/ren-slider.css">
<script type="module" src="rends/components/composites/ren-slider/ren-slider.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<label for="volume">Volume</label><ren-slider show-value><div class="ren-slider-track"><input id="volume" type="range" min="0" max="100" step="1" value="50"></div></ren-slider>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-slider`
- `.ren-slider-danger`
- `.ren-slider-label`
- `.ren-slider-lg`
- `.ren-slider-mark`
- `.ren-slider-mark-label`
- `.ren-slider-mark-tick`
- `.ren-slider-marks`
- `.ren-slider-range`
- `.ren-slider-sm`
- `.ren-slider-success`
- `.ren-slider-track`
- `.ren-slider-track-input`
- `.ren-slider-value`
- `.ren-slider-vertical`
- `.ren-slider-warning`

## States And Attributes

- `:active`
- `:disabled`
- `:focus-visible`
- `:hover`
- `type="range"`

## Public Token API

- `--ren-slider-duration`
- `--ren-slider-easing`
- `--ren-slider-fill-color`
- `--ren-slider-thumb-bg`
- `--ren-slider-thumb-border`
- `--ren-slider-thumb-shadow`
- `--ren-slider-thumb-size`
- `--ren-slider-track-bg`
- `--ren-slider-track-height`
- `--ren-slider-track-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-slider/ren-slider.css`
- `components/composites/ren-slider/ren-slider.js`
- `docs/components/ren-slider.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-tabs
type: contract
name: ren-tabs component.md
path: components/composites/ren-tabs/component.md
data: {"kind":"composite"}
body:
# ren-tabs Component Contract

Tabbed interface with trigger list and panels.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-tabs` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-tabs` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Tabs composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a tablist that shows one panel at a time with mutually exclusive triggers."
    - "You need ARIA tablist semantics (role=tablist, role=tab, role=tabpanel) plus arrow-key keyboard navigation."
    - "You need a default-value (index or tab id) to control which panel renders on first paint."
    - "You need manual activation (arrow moves focus, Enter/Space selects) or automatic activation (arrow selects)."
    - "You need a visual variant — .ren-tab-list-underline (default), .ren-tab-list-pills, or .ren-tab-list-enclosed."
    - "The layout must adapt to narrow widths via container query (horizontal scroll on the .ren-tab-list)."
  avoidWhen:
    - "Each section should be visible simultaneously — use ren-accordion (multiple) or simple sections."
    - "The control switches the entire page route, not in-page content — use ren-nav or anchor links."
    - "Only two states are needed — use ren-toggle-group with a single selection."
    - "You need a stepper/wizard with sequential progress — use ren-stepper."

canonicalImports:
  css:
    - "rends/components/composites/ren-tabs/ren-tabs.css"
  js:
    - "rends/components/composites/ren-tabs/ren-tabs.js"
  notes:
    - "JS is required: it sets up roving tabindex, ARIA wiring, and the ren-tab-change event. CSS-only renders triggers but not the selection state."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "<ren-tabs> wraps a <div role=\"tablist\" class=\"ren-tab-list\"> followed by sibling <div role=\"tabpanel\" class=\"ren-tab-panel\"> elements."
  - "Each tab trigger is a real <button role=\"tab\" class=\"ren-tab\">; never use <a> or <div> for the trigger."
  - "Tab triggers and tabpanels must match 1:1 in document order; the component pairs them by index."
  - "Inactive panels carry the hidden attribute (the component toggles it); do not use display: none manually."
  - "Provide aria-label on <ren-tabs> when there are multiple tablists on the page, so screen-readers can distinguish them."

forbiddenPatterns:
  - "Wrapping a <a href> as the tab trigger — tabs change the visible panel, they are not navigation links."
  - "Setting aria-selected manually on a tab; let the component drive it via _selectedIndex."
  - "Hiding inactive panels with display: none in CSS overrides — use the hidden attribute the JS manages."
  - "Adding interactive elements (buttons, links) directly inside .ren-tab — keep the trigger a single activation surface."
  - "Removing the .ren-tab:focus-visible outline without restoring a visible focus indicator inside the inset offset."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-tabs-active-color, --ren-tabs-border-color, --ren-tabs-color, --ren-tabs-duration, --ren-tabs-easing, --ren-tabs-font-size, --ren-tabs-font-weight, --ren-tabs-gap, --ren-tabs-height, --ren-tabs-indicator-color."
    - "Semantic tokens: --color-text, --color-text-muted, --color-accent, --color-border, --color-fill, --color-fill-hover, --color-surface."
    - "Shape / motion tokens: --radius-sm, --radius-md, --space-*, --size-lg, --label-md-size, --label-md-weight, --label-lg-weight, --transition-tactile."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides for the active underline or pill background."
    - "Hardcoded transition durations on the .ren-tab hover; route through --transition-tactile."

accessibility:
  required:
    - "Triggers are real <button role=\"tab\"> with aria-selected reflecting state and tabindex managed by roving (only the selected tab is tabindex=0)."
    - "Tabpanels carry role=\"tabpanel\" and an aria-labelledby pointing at their trigger id (the component fills this in)."
    - "Arrow Left/Right (horizontal) or Up/Down (vertical) moves focus; Home / End jump to first / last; Enter / Space activate in manual mode."
    - "The .ren-tab meets touch target via min-height: var(--size-lg); do not shrink it below this in touch contexts."
    - "Active tab is distinguished by color AND the underline / pill / enclosed visual — never rely on color alone."
    - "Focus-visible ring is provided by outline + outline-offset on .ren-tab and the panel; preserve both."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-tabs/ren-tabs.css">
<script type="module" src="rends/components/composites/ren-tabs/ren-tabs.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-tabs aria-label="Account settings">
  <div class="ren-tab-list" role="tablist"><button class="ren-tab" role="tab">Profile</button><button class="ren-tab" role="tab">Security</button></div>
  <div class="ren-tab-panel" role="tabpanel">Profile settings</div><div class="ren-tab-panel" role="tabpanel" hidden>Security settings</div>
</ren-tabs>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-tab`
- `.ren-tab-list`
- `.ren-tab-list-enclosed`
- `.ren-tab-list-pills`
- `.ren-tab-list-underline`
- `.ren-tab-panel`
- `.ren-tabs`

## States And Attributes

- `[aria-disabled]`
- `[aria-selected]`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-tabs-active-color`
- `--ren-tabs-border-color`
- `--ren-tabs-color`
- `--ren-tabs-duration`
- `--ren-tabs-easing`
- `--ren-tabs-font-size`
- `--ren-tabs-font-weight`
- `--ren-tabs-gap`
- `--ren-tabs-height`
- `--ren-tabs-indicator-color`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-tabs/ren-tabs.css`
- `components/composites/ren-tabs/ren-tabs.js`
- `docs/components/ren-tabs.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-toast
type: contract
name: ren-toast component.md
path: components/composites/ren-toast/component.md
data: {"kind":"composite"}
body:
# ren-toast Component Contract

Transient notification system with live-region announcements.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-toast` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-toast` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Toast composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a transient notification anchored to a viewport corner that auto-dismisses after a duration."
    - "The notification must NOT steal focus or block the page (use ren-dialog for that)."
    - "You need a status variant — success / info / warning / danger / loading — with matching icon and subtle status surface."
    - "You need a polite or assertive live-region announcement for screen-readers (handled via utils/live-region)."
    - "You need the imperative window.toast API (toast.success, toast.danger, toast.promise(...)) instead of declarative markup."
    - "You need swipe-to-dismiss, hover-to-pause timers, and an optional inline action button."
  avoidWhen:
    - "The message blocks the workflow and demands a decision — use ren-dialog (alert variant)."
    - "The message is persistent / inline page-level — use ren-banner or ren-alert."
    - "The message describes a form field state — use the helper-text / error slot on ren-field."
    - "The disclosure is a contextual popover from a trigger — use ren-popover or ren-tooltip."

canonicalImports:
  css:
    - "rends/components/composites/ren-toast/ren-toast.css"
  js:
    - "rends/components/composites/ren-toast/ren-toast.js"
  notes:
    - "JS is required: it owns the viewport queue, timers, live-region announcements, and swipe handling. window.toast is exposed as the imperative API."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Place one <ren-toast-viewport> (or <div class=\"ren-toast-viewport\" role=\"region\" aria-label=\"Notifications\">) per position; data-position defaults to bottom-right."
  - "Toasts themselves are appended by the JS as .ren-toast nodes with .ren-toast-icon, .ren-toast-body (.ren-toast-title + .ren-toast-description), optional .ren-toast-actions, and .ren-toast-close."
  - "Status variants are applied as a sibling class on .ren-toast: .ren-toast-success / .ren-toast-info / .ren-toast-warning / .ren-toast-danger / .ren-toast-error / .ren-toast-loading."
  - "Action buttons inside .ren-toast-actions reuse .ren-btn (typically .ren-btn-ghost .ren-btn-sm); .ren-toast-action is only an override hook."
  - "Set duration: 0 (or status: 'loading') for persistent toasts; expose a close button so users can dismiss them."

forbiddenPatterns:
  - "Calling toast.show() with status: 'danger' AND focusing the toast — toasts must not steal focus."
  - "Rendering toasts inline in document body without a .ren-toast-viewport host (timers and stacking will not work)."
  - "Hardcoded position offsets via inline style — use the [data-position] attribute and rely on var(--space-4) inset."
  - "Custom slide animations via @keyframes overriding ren-toast-slide-in-*; route motion through --duration-enter / --ease-enter."
  - "Using colored side borders for status emphasis; variants use a subtle status surface plus icon/text instead."
  - "Using a toast to ask for confirmation (\"Are you sure?\") — confirmations belong in ren-dialog."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-toast-anim-duration, --ren-toast-bg, --ren-toast-border, --ren-toast-duration, --ren-toast-easing, --ren-toast-gap, --ren-toast-padding, --ren-toast-radius, --ren-toast-shadow, --ren-toast-width, plus the shared --ren-btn-* family for inline action buttons."
    - "Semantic state tokens: --color-success, --color-success-subtle, --color-info, --color-info-subtle, --color-warning, --color-warning-subtle, --color-danger, --color-danger-subtle, --color-accent (progress bar), --color-surface, --color-surface-raised, --color-surface-hover, --color-surface-active, --color-text, --color-text-muted, --color-border."
    - "Shape / motion tokens: --space-*, --radius-sm, --radius-lg, --shadow-lg, --duration-enter, --duration-exit, --duration-micro, --ease-enter, --ease-exit, --transition-tactile, --z-toast."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides for the status surface, border, or icon color."
    - "Hardcoded animation timings; route through --duration-enter / --duration-exit and the paired easings."

accessibility:
  required:
    - "Viewport is a role=\"region\" with aria-label=\"Notifications\" so screen-readers can locate it as a landmark."
    - "Success / info messages announce via aria-live=\"polite\"; danger / error messages announce via aria-live=\"assertive\" (handled by announcePolite / announceAssertive)."
    - "Status must be communicated by icon AND text/title — color alone does not satisfy the contrast contract."
    - "Hover and keyboard focus pause auto-dismiss timers; restoring focus elsewhere resumes them."
    - "The .ren-toast-close button has an accessible name (aria-label=\"Close\") and is reachable via keyboard."
    - "Toasts never trap focus; they receive focus only when the user tabs into the viewport region intentionally."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-toast/ren-toast.css">
<script type="module" src="rends/components/composites/ren-toast/ren-toast.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-toast-viewport data-position="bottom-right" aria-label="Notifications"></ren-toast-viewport>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-btn`
- `.ren-toast`
- `.ren-toast-action`
- `.ren-toast-actions`
- `.ren-toast-body`
- `.ren-toast-close`
- `.ren-toast-danger`
- `.ren-toast-description`
- `.ren-toast-error`
- `.ren-toast-icon`
- `.ren-toast-info`
- `.ren-toast-loading`
- `.ren-toast-progress`
- `.ren-toast-success`
- `.ren-toast-title`
- `.ren-toast-viewport`
- `.ren-toast-warning`

## States And Attributes

- `[data-closing]`
- `[data-position]`
- `[data-swiping]`
- `[data-toast-id]`
- `:active`
- `:hover`

## Public Token API

- `--ren-btn-bg`
- `--ren-btn-bg-active`
- `--ren-btn-bg-hover`
- `--ren-btn-border-color`
- `--ren-btn-border-width`
- `--ren-btn-color`
- `--ren-btn-duration`
- `--ren-btn-easing`
- `--ren-btn-font-size`
- `--ren-btn-font-weight`
- `--ren-btn-gap`
- `--ren-btn-height`
- `--ren-btn-padding-x`
- `--ren-btn-padding-y`
- `--ren-btn-radius`
- `--ren-btn-ring-color`
- `--ren-btn-ring-offset`
- `--ren-btn-ring-width`
- `--ren-toast-anim-duration`
- `--ren-toast-bg`
- `--ren-toast-border`
- `--ren-toast-duration`
- `--ren-toast-easing`
- `--ren-toast-gap`
- `--ren-toast-padding`
- `--ren-toast-radius`
- `--ren-toast-shadow`
- `--ren-toast-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-toast/ren-toast.css`
- `components/composites/ren-toast/ren-toast.js`
- `docs/components/ren-toast.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-toggle-group
type: contract
name: ren-toggle-group component.md
path: components/composites/ren-toggle-group/component.md
data: {"kind":"composite"}
body:
# ren-toggle-group Component Contract

Grouped toggle controls for mutually exclusive or multi-select modes.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-toggle-group` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-toggle-group` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Toggle Group composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User picks from a small set of mutually exclusive options shown as visually grouped buttons (single mode)."
    - "User toggles multiple independent options that share a control (multiple mode, type=\"multiple\")."
    - "The choices are short labels or icons (text alignment, view density, list/grid mode) better surfaced than in a select."
    - "You want a visual variant — default filled pill group or .ren-toggle-group-outline."
    - "You need size variants (.ren-toggle-group-sm / .ren-toggle-group-lg), full-width fill, or vertical orientation."
  avoidWhen:
    - "The choice set is large or sortable — use ren-select."
    - "The choice is binary on/off for a single concept — use ren-switch or ren-checkbox."
    - "Items are commands rather than state — use ren-toolbar."
    - "Items are pages or routes — use ren-tabs or ren-nav."

canonicalImports:
  css:
    - "rends/components/composites/ren-toggle-group/ren-toggle-group.css"
  js:
    - "rends/components/composites/ren-toggle-group/ren-toggle-group.js"
  notes:
    - "JS is required: it wires role=button, roving tabindex, aria-pressed updates, keyboard navigation, and the ren-toggle-group-change event."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "<ren-toggle-group type=\"single|multiple\" value=\"...\"> wraps real <button class=\"ren-toggle-group-item\" data-value=\"...\"> elements (the component will also accept [role=button] nodes)."
  - "Each item must carry a data-value attribute or a stable textContent — the component uses it to drive selection and the change event."
  - "Set type=\"single\" for mutually-exclusive selection (the component clears other aria-pressed values) or type=\"multiple\" for independent toggles."
  - "Variants are applied on the host: .ren-toggle-group-outline, .ren-toggle-group-sm / -lg, .ren-toggle-group-full, .ren-toggle-group-vertical."
  - "Icons inside items must be <svg> or [role=\"img\"] children sized to 1em (handled by the CSS); accompany icons with text or aria-label."

forbiddenPatterns:
  - "<a href> as items — toggle-group is for state, not navigation."
  - "Setting aria-pressed manually on items; let the component drive it via setValue / click handling."
  - "Using ren-btn inside the group as items — they bring their own chrome; use plain <button class=\"ren-toggle-group-item\"> instead."
  - "Hardcoding the pressed background (background: #fff) — use --color-surface / --color-accent semantic tokens and the outline variant for accent fills."
  - "Removing the focus-visible outline without restoring an outline-offset visible ring."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-toggle-active-bg, --ren-toggle-active-color, --ren-toggle-bg, --ren-toggle-color, --ren-toggle-font-size, --ren-toggle-font-weight, --ren-toggle-height, --ren-toggle-padding-x, --ren-toggle-radius."
    - "Semantic tokens: --color-fill, --color-surface, --color-text, --color-text-muted, --color-accent, --color-border, --color-border-strong."
    - "Shape / motion tokens: --radius-md, --radius-lg, --space-*, --shadow-sm, --touch-min, --transition-tactile, font-size tokens (--font-size-label, --font-size-body, --font-size-xs)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides for the pressed background or accent border."
    - "Hardcoded transition durations on item hover; route through --transition-tactile."

accessibility:
  required:
    - "Items are real <button> elements with role=\"button\" and aria-pressed reflecting state; roving tabindex ensures only one item is tabindex=0 at a time."
    - "Arrow Right/Down moves focus to the next item, Arrow Left/Up to the previous (wrapping); Home / End jump to first / last."
    - "Touch target meets calc(var(--touch-min) - 4px); .ren-toggle-group-sm should only be used in non-touch contexts."
    - "Active state communicates via background AND font-weight / box-shadow — never rely on color alone."
    - "Focus-visible outline is 2px solid var(--color-accent) with outline-offset; preserve it across all variants."
    - "Disabled items use :disabled with pointer-events: none so they remain in tab order semantics but cannot be activated."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-toggle-group/ren-toggle-group.css">
<script type="module" src="rends/components/composites/ren-toggle-group/ren-toggle-group.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-toggle-group type="single" value="left"><button class="ren-toggle-group-item" type="button" data-value="left">Left</button><button class="ren-toggle-group-item" type="button" data-value="center">Center</button><button class="ren-toggle-group-item" type="button" data-value="right">Right</button></ren-toggle-group>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-toggle-group`
- `.ren-toggle-group-full`
- `.ren-toggle-group-item`
- `.ren-toggle-group-lg`
- `.ren-toggle-group-outline`
- `.ren-toggle-group-sm`
- `.ren-toggle-group-vertical`

## States And Attributes

- `[aria-pressed]`
- `[data-state]`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-toggle-active-bg`
- `--ren-toggle-active-color`
- `--ren-toggle-bg`
- `--ren-toggle-color`
- `--ren-toggle-font-size`
- `--ren-toggle-font-weight`
- `--ren-toggle-height`
- `--ren-toggle-padding-x`
- `--ren-toggle-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-toggle-group/ren-toggle-group.css`
- `components/composites/ren-toggle-group/ren-toggle-group.js`
- `docs/components/ren-toggle-group.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-toolbar
type: contract
name: ren-toolbar component.md
path: components/composites/ren-toolbar/component.md
data: {"kind":"composite"}
body:
# ren-toolbar Component Contract

Grouped command bar with roving keyboard navigation.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-toolbar` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-toolbar` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Toolbar composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a grouped command bar (formatting actions, edit operations, view controls) co-located near content."
    - "You need the WAI-ARIA Toolbar pattern: Arrow keys move between items, Tab moves focus in / out of the toolbar."
    - "You need Home / End to jump to first / last item and skip disabled ones."
    - "Some items are toggleable (aria-pressed / [data-active] for sticky on-states like Bold)."
    - "You need a visual separator (.ren-toolbar-separator) to group related commands within the bar."
    - "You need icon-only items via .ren-toolbar-item-icon (aspect-ratio 1) or a ghost variant (.ren-toolbar-ghost)."
  avoidWhen:
    - "Items represent value selection rather than commands — use ren-toggle-group."
    - "Items represent navigation to other pages — use ren-nav or ren-sidebar."
    - "You need to switch between content panels — use ren-tabs."
    - "There is only one action — use ren-button directly."

canonicalImports:
  css:
    - "rends/components/composites/ren-toolbar/ren-toolbar.css"
  js:
    - "rends/components/composites/ren-toolbar/ren-toolbar.js"
  notes:
    - "JS is required for the roving tabindex pattern: import initToolbar / initAllToolbars from ren-toolbar.js."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "The host is <div class=\"ren-toolbar\" role=\"toolbar\" aria-label=\"<purpose>\">; the role and an aria-label are required so AT can announce it."
  - "Items are real <button class=\"ren-toolbar-item\"> with tabindex=\"0\" on the first item and tabindex=\"-1\" on the rest (roving tabindex)."
  - "Separators are <div class=\"ren-toolbar-separator\" role=\"separator\"> between logical groups within the toolbar."
  - "Sticky toggle items reflect state via aria-pressed=\"true|false\" or [data-active]; do not mix the two on the same item."
  - "Use .ren-toolbar-vertical on the host to switch arrow keys to Up/Down and turn separators horizontal."

forbiddenPatterns:
  - "Omitting role=\"toolbar\" or aria-label on the host — initToolbar bails out and the keyboard pattern will not attach."
  - "Setting tabindex=\"0\" on every item — only the focused item should be tabindex=\"0\"; the JS rewrites the rest to -1."
  - "Using <a href> as items — toolbar items invoke commands, not navigate; use ren-nav for links."
  - "Hardcoded gap / padding in inline styles; use --space-1 via the documented selectors."
  - "Mounting toolbar buttons as .ren-btn — toolbar owns its own item chrome; mixing the two doubles the focus rings and padding."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-toolbar-active-bg, --ren-toolbar-bg, --ren-toolbar-border, --ren-toolbar-height, --ren-toolbar-item-hover, --ren-toolbar-item-radius, --ren-toolbar-item-size, --ren-toolbar-padding, --ren-toolbar-radius."
    - "Semantic tokens: --color-text, --color-accent, --color-border, --color-fill-hover, --color-fill-active, --color-surface, --color-surface-raised, --color-surface-sunken, --color-disabled-text, --color-focus-ring."
    - "Shape / motion tokens: --radius-sm, --radius-md, --space-1, --space-2, --size-sm, --stroke-1, --ring-width, --shadow-xs, --transition-tactile, --weight-medium, --caption-size, --text-sm."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named color values in overrides for the pressed-state background or active accent color."
    - "Hardcoded transition durations on item hover; route through --transition-tactile."

accessibility:
  required:
    - "Host carries role=\"toolbar\" plus an aria-label describing its purpose (e.g. \"Formatting\")."
    - "Roving tabindex: only one .ren-toolbar-item is tabindex=\"0\" at any time; Tab enters/exits the toolbar instead of cycling items."
    - "Arrow Left/Right (horizontal) or Up/Down (.ren-toolbar-vertical) moves focus; Home / End jump to first / last; disabled items are skipped."
    - "Toggle items expose aria-pressed and a visible non-color cue (box-shadow + accent color) in addition to background fill."
    - "Focus-visible ring is var(--ring-width) solid var(--color-focus-ring) with outline-offset: -1px; preserve it across variants."
    - "Disabled items use :disabled or aria-disabled=\"true\"; both block keyboard activation and are filtered out of the focus rotation."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-toolbar/ren-toolbar.css">
<script type="module" src="rends/components/composites/ren-toolbar/ren-toolbar.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-toolbar" role="toolbar" aria-label="Formatting"><button class="ren-toolbar-item" type="button" tabindex="0">Bold</button><div class="ren-toolbar-separator" role="separator"></div><button class="ren-toolbar-item" type="button" tabindex="-1">Italic</button></div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-toolbar`
- `.ren-toolbar-ghost`
- `.ren-toolbar-item`
- `.ren-toolbar-item-icon`
- `.ren-toolbar-separator`
- `.ren-toolbar-vertical`

## States And Attributes

- `[aria-disabled]`
- `[aria-pressed]`
- `[data-active]`
- `:active`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-toolbar-active-bg`
- `--ren-toolbar-bg`
- `--ren-toolbar-border`
- `--ren-toolbar-height`
- `--ren-toolbar-item-hover`
- `--ren-toolbar-item-radius`
- `--ren-toolbar-item-size`
- `--ren-toolbar-padding`
- `--ren-toolbar-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-toolbar/ren-toolbar.css`
- `components/composites/ren-toolbar/ren-toolbar.js`
- `docs/components/ren-toolbar.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:composite:ren-tooltip
type: contract
name: ren-tooltip component.md
path: components/composites/ren-tooltip/component.md
data: {"kind":"composite"}
body:
# ren-tooltip Component Contract

Short accessible description attached to a trigger.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-tooltip` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-tooltip` composite.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Tooltip composite behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this composite.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a short, non-interactive description attached to a hoverable / focusable trigger (icon button label, truncated text expansion)."
    - "The content is plain text under ~15rem and provides supplementary info, not essential interaction."
    - "Disclosure is driven by hover, focus, and the popover API with anchor positioning + flip-block fallback."
    - "You need the dark high-contrast pill with an optional arrow caret (.ren-tooltip-arrow)."
    - "You can place [data-side=\"top|right|bottom|left\"] on the .ren-tooltip to indicate preferred side; CSS handles flipping."
  avoidWhen:
    - "The content is interactive (links, buttons, form fields) — use ren-popover or ren-hover-card."
    - "The content is critical for completing the task — use inline helper text or a visible label."
    - "The disclosure is value selection — use ren-select or ren-menu."
    - "The trigger has no accessible name and the tooltip is the only label — give the trigger a real aria-label or visible text instead."

canonicalImports:
  css:
    - "rends/components/composites/ren-tooltip/ren-tooltip.css"
  js:
    - "rends/components/composites/ren-tooltip/ren-tooltip.js"
  notes:
    - "JS is required: it owns the popover toggle, hover/focus timing (--ren-tooltip-delay), and the fallback positioning for browsers without CSS anchor-name support."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Pair a .ren-tooltip-trigger (real <button> or focusable element) with a .ren-tooltip element referenced via the popover API (popovertarget / id linkage)."
  - "The tooltip carries popover attribute so it lives in the top layer; do not implement it as a plain absolutely-positioned <div>."
  - "Include .ren-tooltip-arrow as a child of .ren-tooltip when an arrow is desired; position is driven by [data-side]."
  - "Set the desired side on the tooltip via data-side=\"top|right|bottom|left\"; CSS uses position-area + position-try-fallbacks: flip-block for safe placement."
  - "Tooltip content must be plain text — no buttons, links, or form fields (those break the contract; use ren-popover instead)."

forbiddenPatterns:
  - "Putting interactive elements inside .ren-tooltip — pointer-events: none on the tooltip means clicks would not work and screen readers would not focus them anyway."
  - "Showing the tooltip only on hover without keyboard focus support — every trigger must reveal the tooltip on :focus-visible."
  - "Using a tooltip as the sole accessible name for an icon button — provide aria-label on the trigger and let the tooltip echo it."
  - "Animating the tooltip via custom @keyframes; rely on the documented opacity + transform transition with @starting-style."
  - "Hardcoding background / color on .ren-tooltip — override --ren-tooltip-bg / --ren-tooltip-color tokens instead."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-tooltip-bg, --ren-tooltip-color, --ren-tooltip-delay, --ren-tooltip-duration, --ren-tooltip-easing, --ren-tooltip-font-size, --ren-tooltip-padding-x, --ren-tooltip-padding-y, --ren-tooltip-radius, --ren-tooltip-shadow."
    - "Shape / motion tokens: --radius-md, --space-1, --space-2, --size-caption, --duration-enter, --ease-enter, --z-tooltip."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code. Note: the current default uses --color-gray-900 as the surface; consumers should override --ren-tooltip-bg with a semantic token (e.g. --color-text) rather than reaching for another --gray-* directly."
    - "Hardcoded hex / rgb / named color values (e.g. background: #111, color: white) in overrides — use --ren-tooltip-bg and --ren-tooltip-color."
    - "Hardcoded transition durations; route through --duration-enter / --ren-tooltip-duration and the paired easings."

accessibility:
  required:
    - "Trigger has its own accessible name (visible text or aria-label); the tooltip supplements but does not replace it."
    - "Tooltip opens on both :hover and keyboard :focus-visible; closing on blur / mouseleave / Escape."
    - "Hover delay respects --ren-tooltip-delay; do not show tooltips instantly on mouse jitter or hide them on the next mousemove."
    - "Tooltip text must meet contrast for the chosen --ren-tooltip-bg / --ren-tooltip-color pair (default dark bg / white text is WCAG AA against typical surfaces)."
    - "Touch users: tooltips should not be the only way to reveal essential info — provide a visible label or alternate disclosure for touch."
    - "Tooltip is pointer-events: none and never receives focus — if the content needs interaction, switch to ren-popover."
    - "The component appends its ID to any existing aria-describedby token list and removes only that owned token on disconnect."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/composites/ren-tooltip/ren-tooltip.css">
<script type="module" src="rends/components/composites/ren-tooltip/ren-tooltip.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<button class="ren-tooltip-trigger" type="button" aria-label="More information">Info<ren-tooltip id="info-tip" placement="top">More information</ren-tooltip></button>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-open`
- `.ren-tooltip`
- `.ren-tooltip-arrow`
- `.ren-tooltip-trigger`

## States And Attributes

- `[data-side]`

## Public Token API

- `--ren-tooltip-bg`
- `--ren-tooltip-color`
- `--ren-tooltip-delay`
- `--ren-tooltip-duration`
- `--ren-tooltip-easing`
- `--ren-tooltip-font-size`
- `--ren-tooltip-padding-x`
- `--ren-tooltip-padding-y`
- `--ren-tooltip-radius`
- `--ren-tooltip-shadow`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/composites/ren-tooltip/ren-tooltip.css`
- `components/composites/ren-tooltip/ren-tooltip.js`
- `docs/components/ren-tooltip.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-ai
type: contract
name: ren-ai pattern.md
path: components/patterns/ren-ai/pattern.md
data: {"kind":"pattern"}
body:
# ren-ai Pattern Contract

AI interaction pattern for prompts, responses, suggestions, and assistant surfaces.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-ai` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-ai` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Ai pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The UI surfaces AI-generated content (chat reply, completion, summary, suggestion)."
    - "You need an AI message bubble with streaming caret (.ren-ai-streaming) or typing indicator."
    - "You need to render citations / source references attached to AI output (.ren-ai-citation, .ren-ai-sources)."
    - "You need a confidence indicator with high/medium/low levels (data-level=\"high|medium|low\")."
    - "You need a chat-style prompt input (.ren-ai-prompt + .ren-ai-prompt-input + .ren-ai-prompt-send)."
    - "You need to mark non-AI content as AI-augmented via an inline slug (.ren-ai-slug)."
  avoidWhen:
    - "The content is a generic human message — use a plain card / message bubble."
    - "The control is a generic submit button — use ren-button, not .ren-ai-prompt-send."
    - "The loading state is for non-AI fetch/page — use ren-skeleton, not .ren-ai-skeleton."

canonicalImports:
  css:
    - "rends/components/patterns/ren-ai/ren-ai.css"
  notes:
    - "CSS-only pattern. No colocated JS — streaming text and feedback handlers live in consumer code."
    - "Animations respect prefers-reduced-motion via fallback rules; do not override."

requiredMarkup:
  - "Wrap AI replies in <div class=\"ren-ai-message\"> with a .ren-ai-message-header (slug + meta) and .ren-ai-message-content."
  - "Streaming responses add the .ren-ai-streaming class on .ren-ai-message; the blinking caret is the ::after on .ren-ai-message-content."
  - "Citations are inline <a class=\"ren-ai-citation\"> with vertical-align: super; source lists use <a class=\"ren-ai-source\"> rows inside .ren-ai-sources."
  - ".ren-ai-confidence MUST carry data-level=\"high|medium|low\"; the fill width and color are driven by that attribute."
  - "The prompt composer must be .ren-ai-prompt wrapping a <textarea class=\"ren-ai-prompt-input\"> and a <button class=\"ren-ai-prompt-send\"> with an accessible name."

forbiddenPatterns:
  - "Using .ren-ai-message for non-AI chat content (use a neutral message component instead)."
  - "Faking confidence levels by inline-styling .ren-ai-confidence-fill width — set data-level."
  - "Replacing .ren-ai-prompt-send with a bare <button> styled as a circle — use the documented selector."
  - "Hardcoding the AI accent (e.g. #8B5CF6, --purple-*) instead of using --color-ai / --color-ai-subtle."
  - "Adding colored side borders to mark AI content; use the AI surface, icon/slug, and semantic tokens instead."
  - "Animating the typing dots or skeleton manually; the pattern ships keyframes + reduced-motion fallbacks."

tokenPolicy:
  allowed:
    - "Semantic tokens: --color-ai, --color-ai-hover, --color-ai-subtle, --color-on-ai, --color-text, --color-text-muted, --color-surface-raised, --color-surface-sunken, --color-fill, --color-border-muted, --color-success, --color-warning, --color-danger."
    - "Layout / type tokens: --space-*, --radius-*, --stroke-1, --body-size, --caption-size, --caption-sm-size, --label-size, --leading-relaxed, --weight-medium, --weight-semibold, --weight-bold."
    - "Motion tokens: --duration-tactile, --duration-state, --ease-enter."
    - "Size tokens for the send button and action chrome: --size-sm, --size-md, --text-lg."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code or inside the pattern CSS."
    - "Hardcoded hex / named colors (e.g. #8B5CF6, purple, rgb(...))."
    - "Raw transition durations or easings; use --duration-* / --ease-*."

accessibility:
  required:
    - "AI messages must include a visible AI slug or .ren-ai-message-header so screen-reader users know the source is AI-generated, not human."
    - "Streaming caret and typing dots respect prefers-reduced-motion; never strip those fallbacks."
    - ".ren-ai-action-thumbs feedback buttons need accessible names (aria-label=\"Helpful\" / \"Not helpful\"); icon-only is not enough."
    - ".ren-ai-prompt-input is a real <textarea> with an associated <label> (or aria-label); the send button must reflect disabled state via the disabled attribute, not opacity alone."
    - "Confidence must not rely on color alone — keep the numeric / textual level visible next to .ren-ai-confidence-bar."
    - ".ren-ai-citation must be a real <a> with href and an accessible target; do not implement as a styled <span>."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-ai/ren-ai.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-ai-message"><div class="ren-ai-message-header"><span class="ren-ai-slug">AI</span></div><div class="ren-ai-message-content">Here is the summary.</div></div><form class="ren-ai-prompt"><textarea class="ren-ai-prompt-input" aria-label="Message"></textarea><button class="ren-ai-prompt-send" type="submit" aria-label="Send message">Send</button></form>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-ai-action`
- `.ren-ai-action-edit`
- `.ren-ai-action-regenerate`
- `.ren-ai-action-thumbs`
- `.ren-ai-actions`
- `.ren-ai-citation`
- `.ren-ai-confidence`
- `.ren-ai-confidence-bar`
- `.ren-ai-confidence-fill`
- `.ren-ai-error`
- `.ren-ai-error-message`
- `.ren-ai-file-chip`
- `.ren-ai-file-chip-icon`
- `.ren-ai-file-chip-name`
- `.ren-ai-file-chip-remove`
- `.ren-ai-file-chip-size`
- `.ren-ai-message`
- `.ren-ai-message-content`
- `.ren-ai-message-footer`
- `.ren-ai-message-header`
- `.ren-ai-prompt`
- `.ren-ai-prompt-input`
- `.ren-ai-prompt-send`
- `.ren-ai-skeleton`
- `.ren-ai-skeleton-line`
- `.ren-ai-slug`
- `.ren-ai-source`
- `.ren-ai-source-number`
- `.ren-ai-source-title`
- `.ren-ai-source-url`
- `.ren-ai-sources`
- `.ren-ai-streaming`
- `.ren-ai-tool-call`
- `.ren-ai-tool-call-args`
- `.ren-ai-tool-call-header`
- `.ren-ai-tool-call-name`
- `.ren-ai-tool-call-result`
- `.ren-ai-tool-call-status`
- `.ren-ai-typing`
- `.ren-ai-typing-dots`

All selectors are CSS-only — the consumer wires retry handlers, file
upload state, tool execution, and edit-and-resend logic. The pattern
gives you the visuals (and a sane `<details>` baseline for tool calls
so collapse works without JS); state belongs to your app.

## States And Attributes

- `[data-active]`
- `[data-level]` — on `.ren-ai-confidence` (high|medium|low)
- `[data-status]` — on `.ren-ai-tool-call` (pending|running|success|error)
- `[open]` — native on `.ren-ai-tool-call` when expanded
- `:active`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/patterns/ren-ai/ren-ai.css`
- `docs/components/ren-ai.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-command
type: contract
name: ren-command pattern.md
path: components/patterns/ren-command/pattern.md
data: {"kind":"pattern"}
body:
# ren-command Pattern Contract

Command palette pattern for searchable application actions.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-command` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-command` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Command pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The product needs a Ctrl/Cmd+K command palette / Spotlight-style action launcher."
    - "Actions are searchable by title, description, or data-keywords, with keyboard up/down navigation and Enter to activate."
    - "Items are grouped (e.g. \"Navigation\", \"Settings\") via .ren-command-group with .ren-command-group-heading."
    - "You want a global keyboard shortcut (data-shortcut=\"ctrl+k\") that opens the palette from anywhere on the page."
    - "You need a modal overlay with backdrop + Escape-to-close dismissal driven by a real <dialog>."
  avoidWhen:
    - "The list is a navigation menu — use ren-nav or ren-menubar."
    - "The list is a static dropdown of options — use ren-menu / ren-select."
    - "The list is a filter for a data table — use ren-table-toolbar-search inside ren-table."
    - "There is no search input (just a static action list) — use ren-menu."

canonicalImports:
  css:
    - "rends/components/patterns/ren-command/ren-command.css"
  js:
    - "rends/components/patterns/ren-command/ren-command.js"
  notes:
    - "JS registers customElements.define('ren-command', ...) and owns the global shortcut listener, filtering, roving highlight, and ren-command-select event."
    - "The pattern is dialog-based; the underlying <dialog> must be present so showModal() / close() work."

requiredMarkup:
  - "Use a <ren-command> custom element wrapping a real <dialog class=\"ren-command\"> (the JS calls dialog.showModal())."
  - "Inside, include a .ren-command-input-wrapper with a <input class=\"ren-command-input\" type=\"text\"> and an optional .ren-command-kbd hint."
  - "Wrap action rows in <ul class=\"ren-command-list\"> (or container) of <button class=\"ren-command-item\"> rows; items may carry data-keywords, data-value, data-action."
  - "Group rows under <div class=\"ren-command-group\"><div class=\"ren-command-group-heading\">…</div>…</div>; the JS toggles [data-empty] on empty groups."
  - "Provide a .ren-command-empty fallback element; the JS shows it when the filter produces zero matches."
  - "Each .ren-command-item that has a shortcut puts the keys inside .ren-command-item-shortcut with <kbd> elements."

forbiddenPatterns:
  - "Implementing as a <div> with click handlers — must be <ren-command> + <dialog> so Escape, focus, and a11y come from the platform."
  - "Filtering items via display: none from external code — the component owns visibility via _filterItems()."
  - "Building roving focus / highlight manually — the [data-highlighted] attribute is set by the component on the active item."
  - "Binding a second Ctrl+K listener at the document level — use the data-shortcut attribute or registerAction() API."
  - "Submitting selection from a custom click handler that bypasses the ren-command-select event."

tokenPolicy:
  allowed:
    - "Public component tokens listed in Public Token API: --ren-command-bg, --ren-command-border, --ren-command-item-height, --ren-command-item-radius, --ren-command-max-height, --ren-command-radius, --ren-command-shadow, --ren-command-width."
    - "Semantic surface / text tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-text-faint, --color-fill, --color-fill-hover, --color-separator."
    - "Spacing / motion tokens: --ren-space-*, --ren-radius-*, --duration-enter, --duration-overlay, --duration-state, --ease-enter, --ease-state-change."
    - "Z-index token: --ren-z-modal."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / named colors for surface, border, fill, or text."
    - "Custom z-index values that bypass --ren-z-modal (the palette must layer above content but below toasts)."

accessibility:
  required:
    - "Render as a real <dialog> so focus trap, Escape, and inert background come from the browser."
    - "The input is a real <input type=\"text\"> with an accessible name (label or aria-label like \"Search commands\")."
    - "The component injects a [role=\"status\"][aria-live=\"polite\"] region announcing result counts on every filter — do not remove it."
    - "Items must be real <button> elements (or role=\"option\" rows) so Enter / Space / click all work uniformly."
    - "Disabled items use aria-disabled=\"true\" AND pointer-events: none (the CSS handles the latter)."
    - "The shortcut hint inside .ren-command-kbd is decorative; do not rely on it as the only affordance — Escape and click-outside must both close."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-command/ren-command.css">
<script type="module" src="rends/components/patterns/ren-command/ren-command.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-command><dialog class="ren-command"><div class="ren-command-input-wrapper"><input class="ren-command-input" type="text" aria-label="Search commands"></div><ul class="ren-command-list"><li><button class="ren-command-item" type="button" data-value="save">Save</button></li></ul><div class="ren-command-empty" hidden>No commands found</div></dialog></ren-command>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-command`
- `.ren-command-empty`
- `.ren-command-footer`
- `.ren-command-footer-hint`
- `.ren-command-group`
- `.ren-command-group-heading`
- `.ren-command-input`
- `.ren-command-input-wrapper`
- `.ren-command-item`
- `.ren-command-item-content`
- `.ren-command-item-description`
- `.ren-command-item-icon`
- `.ren-command-item-shortcut`
- `.ren-command-item-title`
- `.ren-command-kbd`
- `.ren-command-list`
- `.ren-command-separator`

## States And Attributes

- `[aria-disabled]`
- `[aria-live]`
- `[data-empty]`
- `[data-highlighted]`
- `:active`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-command-bg`
- `--ren-command-border`
- `--ren-command-item-height`
- `--ren-command-item-radius`
- `--ren-command-max-height`
- `--ren-command-radius`
- `--ren-command-shadow`
- `--ren-command-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/patterns/ren-command/ren-command.css`
- `components/patterns/ren-command/ren-command.js`
- `docs/components/ren-command.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-empty-state
type: contract
name: ren-empty-state pattern.md
path: components/patterns/ren-empty-state/pattern.md
data: {"kind":"pattern"}
body:
# ren-empty-state Pattern Contract

Empty state pattern for no-data, first-run, and recovery surfaces.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-empty-state` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-empty-state` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Empty State pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "A data container (ren-table, list, ren-card, search result) renders zero items and needs a no-data surface."
    - "A first-run experience needs an icon + title + description + primary action (e.g. \"Add your first product\")."
    - "A search / filter result is empty and the surface should explain how to recover (.ren-empty-state-compact)."
    - "An error / recovery state needs the same centered layout as zero-data (e.g. \"Nothing here yet\")."
    - "You want a dashed-bordered placeholder inside a card or table body (.ren-empty-state-bordered)."
  avoidWhen:
    - "The UI is loading data (still pending) — use ren-skeleton, not an empty state."
    - "The page is a real 404 / 500 error route — use the error-page template, not this inline component."
    - "The container is full but a row is unselected — that is a selection-prompt, not an empty state."
    - "You need a toast / inline alert about a failed action — use ren-alert / ren-toast."

canonicalImports:
  css:
    - "rends/components/patterns/ren-empty-state/ren-empty-state.css"
  notes:
    - "CSS-only pattern. No JS — actions inside .ren-empty-state-actions are real <button> / ren-button elements wired by the consumer."
    - "Drop directly into <tbody>, <ul>, or any container — it centers itself with max-width: 28rem and auto inline margins."

requiredMarkup:
  - "Root is <div class=\"ren-empty-state\"> (or a <section> for a landmark)."
  - "Use an .ren-empty-state-icon block for the glyph/illustration; mark decorative icons aria-hidden=\"true\"."
  - "Heading is an <h2> / <h3> with class .ren-empty-state-title — never put plain text without a heading element."
  - "Description text is a <p class=\"ren-empty-state-description\">; keep within the 28rem prose width by not overriding the max-width."
  - "Actions live in <div class=\"ren-empty-state-actions\"> and should be real <button class=\"ren-btn\"> or <a class=\"ren-btn\"> — not links styled as buttons via <span>."

forbiddenPatterns:
  - "Using .ren-empty-state as a generic centered hero — it is reserved for no-data / zero-state surfaces."
  - "Combining .ren-empty-state-compact with .ren-empty-state-bordered when the container is already a bordered card (double border)."
  - "Decorative icon as the only message — always include .ren-empty-state-title so screen readers understand the state."
  - "Hardcoding padding / max-width via inline style — override --ren-empty-padding / --ren-empty-max-width instead."
  - "Embedding form fields directly inside .ren-empty-state — pair with ren-form / ren-button actions only."

tokenPolicy:
  allowed:
    - "Public component tokens listed in Public Token API: --ren-empty-padding, --ren-empty-gap, --ren-empty-icon-size, --ren-empty-title-size, --ren-empty-desc-color, --ren-empty-max-width."
    - "Semantic tokens consumed by selectors: --color-text, --color-text-muted, --color-fill, --color-border."
    - "Layout / type tokens: --space-*, --radius-full, --radius-lg, --width-prose, --text-xl, --text-3xl, --body-size, --caption-size, --weight-semibold, --leading-normal."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for the icon background or borders."
    - "Hardcoded hex / named colors for text, fill, or border."
    - "Custom widths / margins that bypass --ren-empty-max-width; the component is meant to self-center."

accessibility:
  required:
    - "Title is a real heading element (<h2> / <h3>) so the empty state appears in the document outline."
    - "Decorative icons / emoji use aria-hidden=\"true\"; meaningful illustrations need alt text or aria-label."
    - "Primary action is a real <button> or <a> with keyboard activation and a 44px touch target (inherited from .ren-btn)."
    - "Do not communicate the empty state through icon color alone — the title text must remain the source of truth."
    - "If the empty state is announced after an async load, wrap it in role=\"status\" or aria-live=\"polite\" so screen readers learn the new state."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-empty-state/ren-empty-state.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<section class="ren-empty-state"><div class="ren-empty-state-icon" aria-hidden="true">□</div><h2 class="ren-empty-state-title">No projects</h2><p class="ren-empty-state-description">Create a project to get started.</p><div class="ren-empty-state-actions"><button class="ren-btn" type="button">Create project</button></div></section>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-empty-state`
- `.ren-empty-state-actions`
- `.ren-empty-state-bordered`
- `.ren-empty-state-compact`
- `.ren-empty-state-description`
- `.ren-empty-state-icon`
- `.ren-empty-state-title`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-empty-desc-color`
- `--ren-empty-gap`
- `--ren-empty-icon-size`
- `--ren-empty-max-width`
- `--ren-empty-padding`
- `--ren-empty-title-size`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/patterns/ren-empty-state/ren-empty-state.css`
- `docs/components/ren-empty-state.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-form
type: contract
name: ren-form pattern.md
path: components/patterns/ren-form/pattern.md
data: {"kind":"pattern"}
body:
# ren-form

Form validation + layout pattern. Wraps a native `<form>` with declarative
field rules, error summaries, multi-step support, and emit-on-submit
events. Pairs with `ren-field` for control-level wiring.

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `<ren-form>` UI.

## Purpose

The form-level pattern in RenDS. Owns:
- Declarative validation (`data-rules`) with built-in validators
  (`required`, `email`, `min`, `max`, `pattern`, `match`, etc.).
- Validation timing modes (`onSubmit`, `onBlur`, `onChange`, `onTouched`).
- Error summary management at the top of the form.
- Multi-step (wizard) progression via `data-steps`.
- Submitting / disabled state propagation across the form.

Field-level ARIA wiring is delegated to `ren-field`. Layout uses
`ren-stack` / `ren-form-row` / `ren-form-section`.

## Use When

- The page is a real form (login, signup, settings, checkout, multi-step
  wizard) that needs declarative validation rules.
- You want a single error summary at the top of the form, focusable on
  invalid submit.
- You want consistent submitting state and event-driven submission.
- You are using `ren-field` to wrap the controls.

## Do Not Use When

- The "form" is a single search input — use `<form>` + `ren-field` directly.
- The "form" is a chat composer or freeform editor — use a custom flow.
- You need fully-custom field validation that does not fit the
  `data-rules` DSL — use a plain `<form>` with your own JS.
- The interaction is a step-by-step UI without form fields — use
  `ren-tabs` or `ren-empty-state` flows.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The UI is a real form with multiple fields and validation."
    - "You want declarative rules (data-rules=\"required|email|min:8\")."
    - "You want a single error summary that focuses on invalid submit."
    - "You want submitting state to disable the form during async submission."
    - "You want a multi-step wizard with progress indicator."
  avoidWhen:
    - "It is a single-input search/filter — use ren-field alone."
    - "Validation logic is irreducibly custom and the data-rules DSL would not express it."
    - "The flow is not actually a form (chat composer, command palette, etc.)."

canonicalImports:
  css:
    - "rends/components/patterns/ren-form/ren-form.css"
  js:
    - "rends/components/patterns/ren-form/ren-form.js"
  notes:
    - "Always pair with rends/components/primitives/ren-field/ren-field.css + ren-field.js for field wiring."

requiredMarkup:
  - "<ren-form> wraps a real <form>. Do not omit the <form> element."
  - "Each input lives inside <ren-field>; do not use bare <input> at the top level of the form."
  - "Add data-validate=\"onSubmit|onBlur|onChange|onTouched\" on <ren-form>; default is onSubmit."
  - "If using rules, attach data-rules to each <ren-field> (or to the inner control), not to the <form>."
  - "Submit button must be type=\"submit\" inside the <form>."
  - "Provide an empty .ren-form-error-summary with role=\"alert\" tabindex=\"-1\" so the component can populate AND focus it on invalid submit. The component sets tabindex=\"-1\" automatically if you forget."

forbiddenPatterns:
  - "<ren-form> wrapping a <div> instead of a <form>."
  - "Manual aria-invalid / aria-describedby — ren-field owns those."
  - "Submitting via fetch from a custom click handler that bypasses the ren-submit event."
  - "Using a placeholder as the only label."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-field-* for input chrome, --ren-btn-* for actions."
    - "Layout tokens (--space-*) and semantic colors for content."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, etc.)."
    - "Hardcoded colors / sizes that bypass tokens."

accessibility:
  required:
    - "Native <form> semantics; preserve the submit event."
    - "Each control has a <label> via ren-field."
    - "Error summary has role=\"alert\" AND tabindex=\"-1\" so .focus() actually shifts focus on invalid submit. The component sets tabindex=\"-1\" if missing."
    - "Required-state must be explicit (required attribute), not placeholder-only."
    - "Disabled / submitting state cannot strip focus rings."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-form/ren-form.css">
<link rel="stylesheet" href="rends/components/primitives/ren-field/ren-field.css">
<script type="module" src="rends/components/patterns/ren-form/ren-form.js"></script>
<script type="module" src="rends/components/primitives/ren-field/ren-field.js"></script>
```

## Canonical Markup

Single-step login form with onTouched validation:

```html
<ren-form data-validate="onTouched">
  <form class="ren-form ren-stack" novalidate>
    <div class="ren-form-error-summary" role="alert" tabindex="-1" hidden>
      <strong>Please fix the following errors:</strong>
      <ul></ul>
    </div>

    <ren-field data-rules="required|email">
      <label>Email</label>
      <input class="ren-input" type="email" name="email" autocomplete="email" required>
      <span data-error></span>
    </ren-field>

    <ren-field data-rules="required|min:8">
      <label>Password</label>
      <input class="ren-input" type="password" name="password" autocomplete="current-password" required minlength="8">
      <span data-error></span>
    </ren-field>

    <div class="ren-form-actions">
      <button class="ren-btn ren-btn-full" type="submit">Sign in</button>
    </div>
  </form>
</ren-form>

<script type="module">
  document.querySelector('ren-form').addEventListener('ren-submit', (e) => {
    e.preventDefault();
    fetch('/api/login', { method: 'POST', body: JSON.stringify(e.detail.values) });
  });
</script>
```

Multi-step form with progress:

```html
<ren-form data-validate="onSubmit" data-steps="3">
  <form class="ren-form ren-stack">
    <ol class="ren-form-progress" aria-label="Progress">
      <li class="ren-form-step" data-active>
        <span class="ren-form-step-label">Account</span>
      </li>
      <li class="ren-form-step">
        <span class="ren-form-step-label">Profile</span>
      </li>
      <li class="ren-form-step">
        <span class="ren-form-step-label">Review</span>
      </li>
    </ol>
    <!-- step sections … -->
  </form>
</ren-form>
```

## Attributes, Events, and API

- Host attributes:
  - `data-validate="onSubmit|onBlur|onChange|onTouched"` (default: `onSubmit`).
  - `data-steps="N"` enables multi-step mode.
- Field rules: `data-rules="required|email|min:8|max:64|pattern:^[A-Z]+$|match:password"`.
- Submitting state: component sets `[data-submitting]` on `<ren-form>` and
  disables submit buttons while the `ren-submit` consumer is async.
- Events:
  - `ren-submit` — `detail.values` (validated form data). Repeated names are arrays.
    Register async work with `event.detail.waitUntil(promise)`; the
    `[data-submitting]` state remains until every registered promise settles.
    The event is cancelable so consumers may call `preventDefault()` when they
    own submission. A rejected promise emits `ren-submit-error` with
    `detail.error`.
  - `ren-invalid` — `detail.errors` (rule failures).
- Static API: `RenForm.registerValidator(name, fn)` for custom validators.
  Validators may return a Promise; `validateAsync()` and submit await it.
  Use `RenForm.registerMessages(locale, messages)` to localize `required`,
  `email`, `pattern`, `min`, `max`, and `invalid` messages. Set `lang` on
  `<ren-form>` (or `<html>`) for locale selection. `data-persist="key"`
  stores/restores values in `localStorage`; call `reset()` to clear it.

## Variants and Public Selectors

| Class                          | Role                                   |
|--------------------------------|----------------------------------------|
| `.ren-form`                    | Form root chrome.                      |
| `.ren-form-section`            | Visually grouped section.              |
| `.ren-form-section-title`      | Section heading.                       |
| `.ren-form-row`                | Horizontal row of fields.              |
| `.ren-form-actions`            | Footer of action buttons.              |
| `.ren-form-error-summary`      | Top-of-form error summary.             |
| `.ren-form-success`            | Post-submit confirmation slot.         |
| `.ren-form-progress`           | Multi-step progress bar.               |
| `.ren-form-step`               | Single step.                           |
| `.ren-form-step-label`         | Step label text.                       |

## States

| Selector / attr        | Meaning                                                |
|------------------------|--------------------------------------------------------|
| `[data-submitting]`    | Form is processing; submit buttons disabled.           |
| `[data-has-errors]`    | One or more fields are currently invalid.              |
| `[data-invalid]`       | A specific field is invalid.                           |
| `[data-active]`        | Active step in a multi-step form.                      |
| `[data-completed]`     | Completed step.                                        |
| `[data-disabled]`      | A step is locked.                                      |

## Public Token API

Form layout uses `ren-stack` / `--space-*`. Field chrome inherits from
`ren-field`:

- `--ren-field-bg`
- `--ren-field-border-color`
- `--ren-field-border-width`
- `--ren-field-radius`
- `--ren-field-height`
- `--ren-field-padding-x`
- `--ren-field-padding-y`
- `--ren-field-color`
- `--ren-field-placeholder`
- `--ren-field-focus-color`
- `--ren-field-error-color`
- `--ren-field-success-color`

## Accessibility Contract

- The form is a real `<form>` element. `ren-form` is an enhancement.
- Each control lives in `ren-field` (auto-wires label / description / error
  IDs).
- The error summary has `role="alert"` and `tabindex="-1"` (the component
  sets `tabindex="-1"` automatically if it is missing). It receives focus
  on invalid submit so screen readers and keyboard users land on the
  error list.
- `required` is the source of truth for required state; visual indicators
  pair with it.
- Submit buttons are real `<button type="submit">`.
- During submitting state, focus stays on the form (do not yank focus).

## Anti-Patterns

- ❌ `<ren-form><div>…</div></ren-form>` — wrap a real `<form>`.
- ❌ Bare `<input>` at the top of the form without a `ren-field`.
- ❌ Bypassing `ren-submit` and POSTing from the click handler.
- ❌ Hand-writing `aria-invalid` / `aria-describedby` — `ren-field` owns
  these.
- ❌ Using placeholder as a label.

## Related Files

- `components/patterns/ren-form/ren-form.css`
- `components/patterns/ren-form/ren-form.js`
- `components/primitives/ren-field/component.md`
- `docs/components/ren-form.html`
- `ren-design.md`
- `tokens/tokens.md`

## Test Expectations

- Run component / docs a11y coverage on submit / invalid flows.
- Run `npm run lint` after token / selector changes.
- Manually verify keyboard-only submit and error summary focus.


id: contract:pattern:ren-menubar
type: contract
name: ren-menubar pattern.md
path: components/patterns/ren-menubar/pattern.md
data: {"kind":"pattern"}
body:
# ren-menubar Pattern Contract

Application menubar pattern for top-level menu navigation.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-menubar` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-menubar` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Menubar pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The UI is a desktop-style application menubar with persistent top-level menus (File / Edit / View / Help)."
    - "You need WAI-ARIA Menubar semantics: role=\"menubar\", role=\"menu\", role=\"menuitem|menuitemcheckbox|menuitemradio\"."
    - "You need keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End) with roving focus and \"menubar glide\" between triggers."
    - "You need typeahead character matching for fast item selection."
    - "You need checkbox / radio menu items (.ren-menubar-checkbox, .ren-menubar-radio) with aria-checked state."
    - "You need nested submenus with chevron and lateral keyboard navigation."
  avoidWhen:
    - "The nav is a horizontal site bar (Home / About / Pricing) — use ren-nav."
    - "It is a single dropdown / context menu — use ren-menu or ren-popover."
    - "It is a side rail with sections — use ren-sidebar."
    - "It is a Ctrl+K searchable launcher — use ren-command."

canonicalImports:
  css:
    - "rends/components/patterns/ren-menubar/ren-menubar.css"
  js:
    - "rends/components/patterns/ren-menubar/ren-menubar.js"
  notes:
    - "JS owns the keyboard model, roving focus, submenu opening, and ren-menubar-select event. CSS-only fallback only renders static chrome."
    - "Custom element registers as <ren-menubar>; menus are toggled via the [hidden] attribute on .ren-menubar-menu."

requiredMarkup:
  - "Root is <ren-menubar> wrapping <div class=\"ren-menubar\" role=\"menubar\">."
  - "Each top-level menu uses <button class=\"ren-menubar-trigger\" aria-haspopup=\"menu\" aria-expanded=\"false\"> + a sibling <div class=\"ren-menubar-menu\" role=\"menu\" hidden>."
  - "Each item is a <button class=\"ren-menubar-item\" role=\"menuitem\"> (or role=\"menuitemcheckbox|menuitemradio\" for stateful variants)."
  - "Checkbox / radio items carry aria-checked=\"true|false\"; the ::before pseudo renders the check / dot — do not hand-draw it."
  - "Separators are <div class=\"ren-menubar-separator\" role=\"separator\">; submenu chevrons are added automatically by .ren-menubar-submenu::after."
  - "Keyboard shortcuts shown via <span class=\"ren-menubar-shortcut\">⌘N</span>; mirror with a real document-level keydown handler."

forbiddenPatterns:
  - "Building from <a> tags — items must be <button> so Space activates them."
  - "Hiding menus with display: none from external code — toggle the [hidden] attribute; the component owns animation states."
  - "Setting aria-expanded manually from consumer code — the menubar JS manages it on the trigger."
  - "Wrapping items in <li> without role=\"none\" parent — keep the menu role tree flat or wrap with role=\"none\"."
  - "Reimplementing keyboard navigation; the component already handles arrows, typeahead, Home/End, and Escape."

tokenPolicy:
  allowed:
    - "Semantic surface / text tokens: --color-surface, --color-surface-raised, --color-border, --color-separator, --color-text, --color-text-muted, --color-fill-hover, --color-fill-active, --color-accent, --color-on-accent."
    - "Spacing / radius / shadow tokens: --space-1, --space-2, --space-3, --radius-sm, --radius-md, --shadow-lg."
    - "Type tokens: --body-size, --label-size, --label-weight, --caption-size."
    - "Motion tokens: --duration-tactile, --duration-exit, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / named colors for hover, active, or accent."
    - "Replacing the focus outline color with anything other than --color-accent."

accessibility:
  required:
    - "Container has role=\"menubar\" and each <button> is a real menuitem with role=\"menuitem|menuitemcheckbox|menuitemradio\"."
    - "Triggers expose aria-haspopup=\"menu\" and aria-expanded reflects the open/closed state of the associated menu."
    - "Roving tabindex: only one focusable item at a time; arrow keys move focus. The component manages this — do not set tabindex manually on every item."
    - "Checkbox / radio items expose aria-checked; the visual check (::before) must not be the only state cue."
    - "Disabled items use [data-disabled] AND pointer-events: none (CSS handles the latter)."
    - "Focus-visible outline uses --color-accent — never strip without restoring a visible alternative."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-menubar/ren-menubar.css">
<script type="module" src="rends/components/patterns/ren-menubar/ren-menubar.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-menubar><div class="ren-menubar" role="menubar"><button class="ren-menubar-trigger" type="button" aria-haspopup="menu" aria-expanded="false">File</button><div class="ren-menubar-menu" role="menu" hidden><button class="ren-menubar-item" type="button" role="menuitem">New</button></div></div></ren-menubar>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-menubar`
- `.ren-menubar-checkbox`
- `.ren-menubar-item`
- `.ren-menubar-label`
- `.ren-menubar-menu`
- `.ren-menubar-radio`
- `.ren-menubar-separator`
- `.ren-menubar-shortcut`
- `.ren-menubar-submenu`
- `.ren-menubar-trigger`

## States And Attributes

- `[aria-checked]`
- `[aria-expanded]`
- `[data-disabled]`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/patterns/ren-menubar/ren-menubar.css`
- `components/patterns/ren-menubar/ren-menubar.js`
- `docs/components/ren-menubar.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-nav
type: contract
name: ren-nav pattern.md
path: components/patterns/ren-nav/pattern.md
data: {"kind":"pattern"}
body:
# ren-nav Pattern Contract

Navigation pattern for responsive site/app nav and active states.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-nav` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-nav` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Nav pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The product needs a horizontal site / app top navigation bar (brand + links + actions)."
    - "The nav must collapse to a hamburger menu below 48rem (768px) with an animated <span>-stripe toggle."
    - "Active route should be reflected via aria-current=\"page\" (or the .active class) on a .ren-nav-link."
    - "You need optional sticky-on-scroll behavior with a translucent backdrop blur (.ren-nav-sticky)."
    - "You need a transparent variant for hero sections (.ren-nav-transparent)."
    - "You need top-level dropdown menus inside the nav using the native [popover] API (.ren-nav-dropdown)."
  avoidWhen:
    - "The nav is a persistent left/right rail — use ren-sidebar."
    - "The UI is a desktop-style application menubar (File / Edit / View) — use ren-menubar."
    - "The disclosure is a one-off dropdown unrelated to site nav — use ren-menu or ren-popover."
    - "The nav lives inside a card or pattern as secondary tabs — use ren-tabs."

canonicalImports:
  css:
    - "rends/components/patterns/ren-nav/ren-nav.css"
  js:
    - "rends/components/patterns/ren-nav/ren-nav.js"
  notes:
    - "JS registers <ren-nav> and owns the mobile toggle, Escape-to-close, resize handler, and popover-based dropdowns."
    - "CSS-only fallback renders the desktop layout; mobile menu requires JS to toggle [aria-expanded] on .ren-nav-toggle."

requiredMarkup:
  - "Wrap the bar in <ren-nav> containing a <nav class=\"ren-nav\" aria-label=\"…\">."
  - "Brand block uses <a class=\"ren-nav-brand\" href=\"/\"> with a logo + product name; it sits at the inline-start."
  - "Links live in <ul class=\"ren-nav-links\"> of <li><a class=\"ren-nav-link\" href=\"…\"></a></li>; active item adds aria-current=\"page\"."
  - "Mobile hamburger is <button class=\"ren-nav-toggle\" aria-expanded=\"false\" aria-controls=\"…\" aria-label=\"Toggle menu\"> with three <span> stripes inside."
  - "Action area (CTA buttons, theme toggle, sign-in) goes in <div class=\"ren-nav-actions\"> which floats to the inline-end via margin-inline-start: auto."
  - "Dropdowns use .ren-nav-dropdown wrapping a trigger <a class=\"ren-nav-link\" aria-expanded=\"false\" popovertarget=\"…\"> and a [popover] panel."

forbiddenPatterns:
  - "Using <div> stacks instead of <nav> + <ul>/<li> — landmarks and list semantics matter."
  - "Manually toggling display: none on .ren-nav-links for mobile — set [data-open] on .ren-nav or aria-expanded on .ren-nav-toggle."
  - "Styling links with ren-btn — sidebar / nav items own their own chrome via .ren-nav-link."
  - "Replacing the hamburger with a custom toggle that lacks aria-expanded / aria-controls."
  - "Hardcoding the sticky backdrop color — the .ren-nav-sticky variant already uses color-mix on --color-surface."

tokenPolicy:
  allowed:
    - "Public component tokens listed in Public Token API: --ren-nav-bg, --ren-nav-border, --ren-nav-gap, --ren-nav-height, --ren-nav-link-active, --ren-nav-link-color, --ren-nav-padding-x."
    - "Semantic tokens consumed by selectors: --color-surface, --color-border, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-fill (for sticky color-mix)."
    - "Spacing / radius tokens: --ren-space-1, --ren-space-2, --ren-space-3, --ren-space-4, --ren-title-sm-size, --ren-label-size."
    - "Motion tokens: --duration-overlay, --duration-state, --duration-route, --duration-enter, --ease-state-change, --ease-enter."
    - "Z-index token: --ren-z-sticky."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer overrides."
    - "Hardcoded hex / named colors for bg, border, active, or hover."
    - "Custom z-index values that bypass --ren-z-sticky for sticky mode."

accessibility:
  required:
    - "Root is a real <nav> with aria-label (e.g. \"Primary\") so screen readers can list landmarks."
    - "Active route uses aria-current=\"page\" (the .active class is a styling alias, not the source of truth)."
    - ".ren-nav-toggle has aria-expanded reflecting menu state, aria-controls pointing to the links list, and an aria-label (icon-only is not a name)."
    - "Mobile menu closes on Escape and on link activation (the component owns both behaviors)."
    - "Dropdown triggers expose aria-expanded; popover panels are real [popover] elements so the platform manages focus return."
    - "Focus-visible outlines use --color-accent at 2px offset — do not remove without a visible alternative."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-nav/ren-nav.css">
<script type="module" src="rends/components/patterns/ren-nav/ren-nav.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-nav><nav class="ren-nav" aria-label="Primary"><a class="ren-nav-brand" href="/">RenDS</a><ul id="primary-links" class="ren-nav-links"><li><a class="ren-nav-link" href="/docs" aria-current="page">Docs</a></li></ul><button class="ren-nav-toggle" type="button" aria-expanded="false" aria-controls="primary-links" aria-label="Toggle menu"><span></span><span></span><span></span></button></nav></ren-nav>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-nav`
- `.ren-nav-actions`
- `.ren-nav-brand`
- `.ren-nav-dropdown`
- `.ren-nav-link`
- `.ren-nav-links`
- `.ren-nav-sticky`
- `.ren-nav-toggle`
- `.ren-nav-transparent`

## States And Attributes

- `[aria-current]`
- `[aria-expanded]`
- `[data-dropdown]`
- `[data-open]`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-nav-bg`
- `--ren-nav-border`
- `--ren-nav-gap`
- `--ren-nav-height`
- `--ren-nav-link-active`
- `--ren-nav-link-color`
- `--ren-nav-padding-x`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/patterns/ren-nav/ren-nav.css`
- `components/patterns/ren-nav/ren-nav.js`
- `docs/components/ren-nav.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:pattern:ren-sidebar
type: contract
name: ren-sidebar pattern.md
path: components/patterns/ren-sidebar/pattern.md
data: {"kind":"pattern"}
body:
# ren-sidebar

Application sidebar pattern: persistent left/right navigation with desktop
collapse, mobile overlay, active-item state, and keyboard accessibility.
Pairs with a main content slot to form an app shell layout.

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `<ren-sidebar>` UI.

## Purpose

The dashboard / app-shell sidebar pattern in RenDS. Owns:
- Persistent layout with `.ren-sidebar-layout` (sidebar + main content).
- Desktop collapsed / expanded states (`[data-collapsed]`).
- Mobile overlay state (`[data-open]`) with backdrop and Escape / outside
  click dismissal.
- Active item state via `aria-current="page"` plus `.active`.
- Persistence of collapse state in `localStorage`.
- Toggle button + keyboard support.

Sidebar items are real `<a>` (for routes) or `<button>` (for actions); they
are *not* `ren-link` / `ren-btn` — sidebar styling owns their chrome.

## Use When

- The product has persistent left/right navigation across multiple pages.
- The page is an app shell with sidebar + main layout (`.ren-sidebar-layout`).
- The sidebar must collapse on desktop and overlay on mobile.
- Persistence of collapse state across sessions matters.

## Do Not Use When

- The navigation is a top horizontal bar — use `ren-nav`.
- The navigation is a one-off menu inside another component — use
  `ren-menu` / `ren-menubar`.
- The sidebar is a transient panel — use `ren-sheet`.
- The sidebar is a single floating button toolbar — use `ren-toolbar`.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The product needs persistent navigation across many pages."
    - "Desktop should support a collapsed (icon-only) and expanded state."
    - "Mobile should overlay with backdrop dismissal."
    - "Active route should be reflected via aria-current=\"page\"."
    - "Collapse state should persist in localStorage."
  avoidWhen:
    - "The navigation is a horizontal top bar — use ren-nav."
    - "The disclosure is transient or context-specific — use ren-sheet / ren-menu."
    - "The page does not have a multi-section nav."

canonicalImports:
  css:
    - "rends/components/patterns/ren-sidebar/ren-sidebar.css"
  js:
    - "rends/components/patterns/ren-sidebar/ren-sidebar.js"
  notes:
    - "JS handles toggle, persistence, and overlay dismissal. CSS-only fallback only renders the expanded desktop state."

requiredMarkup:
  - "Wrap the app shell in <div class=\"ren-sidebar-layout\">."
  - "<ren-sidebar> is the sidebar root; main content goes in a sibling <main class=\"ren-sidebar-main\">."
  - "Wrap items in <nav class=\"ren-sidebar-nav\"> with <a class=\"ren-sidebar-item\"> children."
  - "Use aria-current=\"page\" on the active item; the .active class is for styling parity."
  - "Provide an aria-label on <ren-sidebar> when multiple navigation landmarks exist on the page."
  - "Toggle button uses .ren-sidebar-toggle and aria-label (the icon alone is not a name)."

forbiddenPatterns:
  - "Wrapping non-nav content in <nav class=\"ren-sidebar-nav\">."
  - "Using ren-btn / ren-link as sidebar items — use .ren-sidebar-item directly."
  - "Hardcoded sidebar widths in inline styles; use --ren-sidebar-width."
  - "Manually toggling display: none for the mobile overlay; use the [data-open] state."

tokenPolicy:
  allowed:
    - "Component tokens: every --ren-sidebar-* listed in Public Token API."
    - "Semantic tokens for content (--color-text, --color-text-muted, etc.)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code."
    - "Hardcoded shadow / overlay colors that bypass tokens."

accessibility:
  required:
    - "Wrap items in a real <nav> landmark."
    - "Active item uses aria-current=\"page\"."
    - "Toggle button has aria-label and toggles aria-expanded if it controls disclosure."
    - "Collapsed icon-only items must keep accessible names from their link text (do not hide it from AT)."
    - "Mobile overlay closes on Escape and outside click."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-sidebar/ren-sidebar.css">
<script type="module" src="rends/components/patterns/ren-sidebar/ren-sidebar.js"></script>
```

## Canonical Markup

App shell with sidebar + main:

```html
<div class="ren-sidebar-layout">
  <ren-sidebar class="ren-sidebar" aria-label="Primary">
    <div class="ren-sidebar-header">
      <a class="ren-sidebar-item" href="/" aria-label="Home">
        <span class="ren-sidebar-item-icon" aria-hidden="true">⌂</span>
        <span class="ren-sidebar-item-text">Acme</span>
      </a>
    </div>

    <nav class="ren-sidebar-nav" aria-label="Main">
      <a class="ren-sidebar-item active" href="/dashboard" aria-current="page">
        <span class="ren-sidebar-item-icon" aria-hidden="true">📊</span>
        <span class="ren-sidebar-item-text">Dashboard</span>
      </a>
      <a class="ren-sidebar-item" href="/projects">
        <span class="ren-sidebar-item-icon" aria-hidden="true">📁</span>
        <span class="ren-sidebar-item-text">Projects</span>
      </a>
    </nav>

    <div class="ren-sidebar-section">
      <div class="ren-sidebar-section-label">Account</div>
      <a class="ren-sidebar-item" href="/settings">
        <span class="ren-sidebar-item-icon" aria-hidden="true">⚙</span>
        <span class="ren-sidebar-item-text">Settings</span>
      </a>
    </div>

    <button class="ren-sidebar-toggle" type="button" aria-label="Toggle sidebar"></button>
  </ren-sidebar>

  <main class="ren-sidebar-main">
    <!-- page content -->
  </main>
</div>
```

Programmatic API:

```js
const sidebar = document.querySelector('ren-sidebar');
sidebar.toggleMenu();
sidebar.setActiveItem('/projects');
sidebar.addEventListener('ren-sidebar-toggle', (e) => console.log(e.detail.collapsed));
```

## Attributes, Events, and API

- Host states:
  - `[data-collapsed]` — desktop collapsed (icon-only).
  - `[data-open]` — mobile overlay open.
- Active item: `.active` class plus `aria-current="page"` on the
  current `.ren-sidebar-item`.
- Methods: `toggleMenu()`, `openMobileMenu()`, `closeMobileMenu()`,
  `setActiveItem(href)`.
- Getters: `isCollapsed`, `isMobileOpen`.
- Event: `ren-sidebar-toggle` (`detail.collapsed`).
- Persistence: collapse state stored in `localStorage` under
  `ren-sidebar-collapsed`.

## Variants and Public Selectors

| Class                            | Role                                |
|----------------------------------|-------------------------------------|
| `.ren-sidebar`                   | Sidebar root chrome.                |
| `.ren-sidebar-layout`            | Layout wrapper (sidebar + main).    |
| `.ren-sidebar-main`              | Main-content slot.                  |
| `.ren-sidebar-header`            | Header slot.                        |
| `.ren-sidebar-content`           | Scrollable middle slot.             |
| `.ren-sidebar-footer`            | Footer slot.                        |
| `.ren-sidebar-nav`               | `<nav>` wrapper.                    |
| `.ren-sidebar-section`           | Grouped section.                    |
| `.ren-sidebar-section-label`     | Section heading text.               |
| `.ren-sidebar-item`              | Single nav item.                    |
| `.ren-sidebar-item-icon`         | Icon slot inside an item.           |
| `.ren-sidebar-item-text`         | Text slot inside an item.           |
| `.ren-sidebar-divider`           | Visual divider between sections.    |
| `.ren-sidebar-toggle`            | Collapse button.                    |
| `.ren-sidebar-overlay`           | Mobile backdrop.                    |

## States

| Selector / attr        | Meaning                                |
|------------------------|----------------------------------------|
| `[aria-current="page"]`| Active route on the current item.      |
| `[aria-disabled]`      | Disabled item (still focusable).       |
| `[data-collapsed]`     | Desktop collapsed (icon-only) state.   |
| `[data-open]`          | Mobile overlay open.                   |
| `:focus-visible`       | Keyboard focus on an item or toggle.   |

## Public Token API

- `--ren-sidebar-bg`
- `--ren-sidebar-border`
- `--ren-sidebar-width`
- `--ren-sidebar-collapsed-width`
- `--ren-sidebar-item-height`
- `--ren-sidebar-item-padding`
- `--ren-sidebar-item-radius`
- `--ren-sidebar-active-bg`
- `--ren-sidebar-active-color`
- `--ren-sidebar-duration`
- `--ren-sidebar-easing`

## Accessibility Contract

- Wrap items in a real `<nav>` landmark and label the sidebar with
  `aria-label` when multiple nav landmarks exist on the page.
- The current route uses `aria-current="page"`.
- Collapsed icon-only items must keep accessible names — do not hide the
  text label from screen readers.
- The toggle button has an `aria-label` (`"Toggle sidebar"`).
- Mobile overlay closes on Escape and outside click.
- Persistence respects user choice; do not auto-collapse on every visit.

## Anti-Patterns

- ❌ Wrapping a `<nav>` of non-RenDS items inside `.ren-sidebar-nav`.
- ❌ Replacing items with `ren-btn` / `ren-link` and re-styling.
- ❌ `<ren-sidebar style="width:240px">` — set `--ren-sidebar-width` on a
  parent scope.
- ❌ Using `display: none` to hide the sidebar on mobile — toggle the
  `[data-open]` state instead.
- ❌ Decorative-icon-only items without text labels.

## Related Files

- `components/patterns/ren-sidebar/ren-sidebar.css`
- `components/patterns/ren-sidebar/ren-sidebar.js`
- `docs/components/ren-sidebar.html`
- `ren-design.md`
- `tokens/tokens.md`

## Test Expectations

- Run component / docs a11y coverage on collapse / mobile overlay flows.
- Run `npm run lint` after token / selector changes.
- Manually verify keyboard navigation: Tab through items, Escape closes
  the mobile overlay.


id: contract:pattern:ren-table
type: contract
name: ren-table pattern.md
path: components/patterns/ren-table/pattern.md
data: {"kind":"pattern"}
body:
# ren-table Pattern Contract

Data table pattern with sorting, row state, selection, and dense scanning.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-table` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-table` pattern.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Table pattern behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this pattern.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The UI is a tabular dataset with rows + columns that needs sortable headers (.ren-th-sortable + data-sort)."
    - "You need row selection (multi-select with shift-click range) using a leading <input type=\"checkbox\"> in .ren-table-select cells."
    - "You need client-side pagination (.ren-table-pagination + data-page-prev/data-page-next/data-page-size-select buttons)."
    - "You need search / filter via a [data-table-search] input inside .ren-table-toolbar."
    - "You need density variants (.ren-table-compact, .ren-table-comfortable) or sticky / pinned headers and columns."
    - "You need integrated empty state (.ren-table-empty) and loading shimmer ([data-loading] + .ren-table-loading-overlay)."
  avoidWhen:
    - "The data is a list of cards / tiles — use ren-card grid, not a table."
    - "There are only 2-3 key/value pairs per item — use ren-description-list."
    - "It is a tree / hierarchical view — use ren-tree."
    - "It is a single read-only stat — use ren-stat / ren-metric."
    - "The dataset is bigger than what fits in memory — pair with server-side pagination, not the built-in client pager."

canonicalImports:
  css:
    - "rends/components/patterns/ren-table/ren-table.css"
  js:
    - "rends/components/patterns/ren-table/ren-table.js"
  notes:
    - "JS registers <ren-table> and owns sorting, selection, pagination, keyboard nav, column resize, and search filter."
    - "Use real <table>, <thead>, <tbody>, <tr>, <th>, <td> — the JS hooks the standard elements; do not replace with divs."

requiredMarkup:
  - "Root is <ren-table data-page-size=\"…\"> wrapping a <div class=\"ren-table-wrapper\"> > <table class=\"ren-table\">."
  - "Header row uses <thead class=\"ren-table-header\"><tr><th class=\"ren-th\">…</th></tr></thead>; sortable headers add .ren-th-sortable + data-column=\"<key>\"."
  - "Body uses <tbody class=\"ren-table-body\">; its rows are <tr class=\"ren-tr\" data-row-id=\"…\"> with <td class=\"ren-td\"> cells. Selection uses class .ren-table-select on both th/td and a real <input type=\"checkbox\" aria-label=\"Select row\">."
  - "Pagination chrome goes in <div class=\"ren-table-pagination\"> with .ren-table-pagination-info + .ren-table-pagination-controls containing buttons carrying [data-page-prev] / [data-page-next] / [data-page-size-select]."
  - "Toolbar lives in <div class=\"ren-table-toolbar\"> and the search box must include the [data-table-search] attribute so the JS wires filtering."
  - "Empty state is <div class=\"ren-table-empty\"> with .ren-table-empty-icon / .ren-table-empty-title / .ren-table-empty-description (or reuse the ren-empty-state pattern)."

forbiddenPatterns:
  - "Replacing <table> with <div role=\"table\"> — kills native keyboard / screen-reader support."
  - "Implementing sort by clicking a non-th element — sort affordance must live on <th class=\"ren-th-sortable\">."
  - "Setting [data-sort] manually from consumer code — the component owns asc/desc cycling."
  - "Custom checkbox UI inside .ren-table-select — keep the native <input type=\"checkbox\"> (accent-color is themed via --color-accent)."
  - "Toggling rows with display: none for filtering — let the JS manage row visibility through its filter pipeline."
  - "Hardcoding pinned column offsets — use .ren-th-pinned / .ren-td-pinned which already set sticky positioning."

tokenPolicy:
  allowed:
    - "Public component tokens listed in Public Token API: --ren-table-border-color, --ren-table-cell-padding, --ren-table-header-bg, --ren-table-header-color, --ren-table-header-size, --ren-table-header-weight, --ren-table-hover-bg, --ren-table-row-height, --ren-table-selected-bg, --ren-table-stripe-bg."
    - "Semantic tokens consumed by selectors: --color-surface, --color-surface-raised, --color-border, --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-accent-subtle."
    - "Spacing / radius / type tokens: --space-2, --space-3, --space-4, --space-10, --radius-sm, --radius-md, --stroke-1, --body-size, --label-size, --label-weight, --caption-size, --touch-min."
    - "Motion tokens: --duration-tactile, --duration-state, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) for stripes, borders, or selection."
    - "Hardcoded hex / named colors for hover, selected, or accent."
    - "Inline transition values — use --duration-* / --ease-* tokens."

accessibility:
  required:
    - "Use a real <table> with <thead> / <tbody>; sortable <th> elements set aria-sort=\"ascending|descending|none\" mirroring [data-sort]."
    - "Selection checkboxes are real <input type=\"checkbox\"> with aria-label (\"Select all\" / \"Select row N\"); rows reflect state via aria-selected=\"true\" on <tr>."
    - "Pagination buttons are real <button> with min-width/height of 44px (--touch-min); disabled state uses the disabled attribute, not opacity alone."
    - "Loading state sets [data-loading] on <ren-table> and exposes pointer-events: none on rows; pair with an aria-live announcement so SR users hear \"Loading…\" / \"Loaded N rows\"."
    - "Empty state must include a visible .ren-table-empty-title text; do not rely on icon alone."
    - "Focus rings on <th>, <td>, .ren-tr (:focus-within), and pagination buttons all use --color-accent — never strip without restoring a visible alternative."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/patterns/ren-table/ren-table.css">
<script type="module" src="rends/components/patterns/ren-table/ren-table.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-table data-page-size="10"><div class="ren-table-wrapper"><table class="ren-table"><thead class="ren-table-header"><tr><th class="ren-th ren-th-sortable" data-column="name">Name</th></tr></thead><tbody class="ren-table-body"><tr class="ren-tr" data-row-id="1"><td class="ren-td">Jane Doe</td></tr></tbody></table></div></ren-table>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-table`
- `.ren-table-body`
- `.ren-table-comfortable`
- `.ren-table-compact`
- `.ren-table-empty`
- `.ren-table-empty-description`
- `.ren-table-empty-icon`
- `.ren-table-empty-title`
- `.ren-table-header`
- `.ren-table-loading-overlay`
- `.ren-table-pagination`
- `.ren-table-pagination-button`
- `.ren-table-pagination-controls`
- `.ren-table-pagination-info`
- `.ren-table-select`
- `.ren-table-toolbar`
- `.ren-table-toolbar-search`
- `.ren-table-wrapper`
- `.ren-td`
- `.ren-td-pinned`
- `.ren-th`
- `.ren-th-pinned`
- `.ren-th-resize`
- `.ren-th-sortable`
- `...and 1 more in the source files.`

## States And Attributes

- `[aria-selected]`
- `[data-loading]`
- `[data-page-next]`
- `[data-page-prev]`
- `[data-page-size-select]`
- `[data-sort]`
- `[data-table-search]`
- `:active`
- `:checked`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-table-border-color`
- `--ren-table-cell-padding`
- `--ren-table-header-bg`
- `--ren-table-header-color`
- `--ren-table-header-size`
- `--ren-table-header-weight`
- `--ren-table-hover-bg`
- `--ren-table-row-height`
- `--ren-table-selected-bg`
- `--ren-table-stripe-bg`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Events

- `ren-sort` — `{ column, direction }` after the sort direction changes.
- `ren-select` — `{ selected }` after row selection changes or is cleared.
- `ren-filter` — `{ value }` after the toolbar search filter changes.

All three events bubble and are composed; they are not cancelable.

## Related Files

- `components/patterns/ren-table/ren-table.css`
- `components/patterns/ren-table/ren-table.js`
- `docs/components/ren-table.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-avatar
type: contract
name: ren-avatar component.md
path: components/primitives/ren-avatar/component.md
data: {"kind":"primitive"}
body:
# ren-avatar Component Contract

User identity primitive for images, initials, and presence indicators.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-avatar` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-avatar` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Avatar primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need to represent a user or entity identity via image, initials, or fallback glyph."
    - "You need a presence / status dot attached to the identity (online, away, busy, offline)."
    - "You need a stack of overlapping identities (collaborators, attendees) via .ren-avatar-group."
    - "You need consistent circular (or .ren-avatar-square) framing across sizes xs / sm / md / lg / xl / 2xl."
    - "The image must crop to a fixed square with object-fit: cover regardless of source aspect ratio."
  avoidWhen:
    - "You only need a status label or count without identity — use ren-badge / ren-badge-dot."
    - "You need a clickable identity menu / profile dropdown — wrap the avatar in ren-menu / ren-popover."
    - "You need a decorative logo or product icon — use a plain <img> or ren-icon."

canonicalImports:
  css:
    - "rends/components/primitives/ren-avatar/ren-avatar.css"
  notes:
    - "CSS-only primitive — no colocated JS exists. Do not import a ren-avatar.js."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Root is <div class=\"ren-avatar\"> containing either an <img> or text initials (max 2 chars); never both as siblings."
  - "<img> children must have alt=\"\" when the avatar is decorative, or alt=\"Name\" when it conveys identity."
  - "Status dot must be wrapped together with the avatar in <div class=\"ren-avatar-wrapper\"> with .ren-avatar-status as a sibling, not a child of .ren-avatar."
  - "Avatar groups use <div class=\"ren-avatar-group\"> with .ren-avatar children only (overlap is owned by the group, not by margins on items)."
  - "Size modifiers (.ren-avatar-xs / -sm / -lg / -xl / -2xl) replace the default md size; do not stack two size classes."

forbiddenPatterns:
  - "Background <img> via CSS background-image — always use a real <img> child for alt-text and lazy-loading."
  - "Hardcoded width / height in inline styles — use a size modifier or override --ren-avatar-size."
  - "Communicating presence with .ren-avatar-status alone without a visually-hidden text label (color is not a name)."
  - "Nesting .ren-avatar inside .ren-avatar (e.g., avatar-on-avatar) — group overlap is the documented pattern."
  - "Custom border-radius via inline style — use .ren-avatar-square or override --ren-avatar-radius."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-avatar-bg, --ren-avatar-border, --ren-avatar-color, --ren-avatar-font-size, --ren-avatar-radius, --ren-avatar-size."
    - "Semantic tokens: --color-fill, --color-text-secondary, --color-surface, --color-success, --color-warning, --color-danger."
    - "Size aliases: --avatar-xs / -sm / -md / -lg / -xl / -2xl and --radius-full / --radius-md."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors for background, border, or status dot."
    - "Bypassing --color-surface for the status ring; the white/dark ring must adapt to theme."

accessibility:
  required:
    - "Provide a meaningful alt attribute on the <img> when the avatar identifies a specific person; use alt=\"\" only when the name is rendered elsewhere."
    - "Initials avatars must have an accessible name via aria-label on the .ren-avatar or visually-hidden text matching the user's full name."
    - "Status indicator must be accompanied by a textual label for screen readers (e.g., visually-hidden \"Online\") — color alone is not sufficient."
    - "Avatar groups should expose a roll-up count or list label (e.g., aria-label=\"3 collaborators\") rather than relying on visual stacking."
    - "Do not attach click handlers to a non-interactive .ren-avatar; wrap in a real <button> or <a> if the identity is actionable."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-avatar/ren-avatar.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-avatar"><img src="/avatars/jane.jpg" alt="Jane Doe"></div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-avatar`
- `.ren-avatar-2xl`
- `.ren-avatar-group`
- `.ren-avatar-lg`
- `.ren-avatar-sm`
- `.ren-avatar-square`
- `.ren-avatar-status`
- `.ren-avatar-status-away`
- `.ren-avatar-status-busy`
- `.ren-avatar-status-offline`
- `.ren-avatar-wrapper`
- `.ren-avatar-xl`
- `.ren-avatar-xs`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-avatar-bg`
- `--ren-avatar-border`
- `--ren-avatar-color`
- `--ren-avatar-font-size`
- `--ren-avatar-radius`
- `--ren-avatar-size`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-avatar/ren-avatar.css`
- `docs/components/ren-avatar.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-badge
type: contract
name: ren-badge component.md
path: components/primitives/ren-badge/component.md
data: {"kind":"primitive"}
body:
# ren-badge Component Contract

Small status, category, count, or state label.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-badge` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-badge` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Badge primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a small inline label for status (success / warning / danger / info), category, count, or tag."
    - "The label is decorative or status-only; it does not navigate or trigger a side effect."
    - "You need a presence dot without text (.ren-badge-dot) — color-coded indicator."
    - "You need a pill-shaped (radius-full) label sized in em so it scales with surrounding text."
    - "The label must sit inline inside a heading, list item, button, or table cell."
  avoidWhen:
    - "The label is a removable chip with an X button — use ren-tag."
    - "The label is a persistent inline message with title/icon/dismiss — use ren-banner."
    - "The label triggers a click action — use ren-btn (or .ren-btn-sm) instead."
    - "You need an avatar / identity surface — use ren-avatar."

canonicalImports:
  css:
    - "rends/components/primitives/ren-badge/ren-badge.css"
  notes:
    - "CSS-only primitive — no colocated JS exists. Do not import a ren-badge.js."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Use <span class=\"ren-badge\"> (inline) — not <div> — so badges flow with surrounding text."
  - "Combine .ren-badge with a single variant modifier (.ren-badge-primary | -secondary | -success | -warning | -danger | -info | -outline); do not stack two variants."
  - "Status badges that convey meaning must include text (or visually-hidden text) — color alone is not sufficient."
  - "Dot indicators use <span class=\"ren-badge-dot ren-badge-dot-success\"> (empty content) and require a sibling text label."
  - "Numeric count badges should set aria-label (e.g., aria-label=\"3 unread\") when the number is ambiguous without context."

forbiddenPatterns:
  - "<button class=\"ren-badge\"> — badges are not interactive; use ren-btn for actions."
  - "Hardcoded background / color overrides via inline style — override --ren-badge-bg / --ren-badge-color instead."
  - "Using .ren-badge as a removable chip with a close button — that is ren-tag's contract."
  - "Color-only status (.ren-badge-danger with no text and no aria-label)."
  - "Setting font-size in px on .ren-badge — the em-based padding will desynchronize from the text scale."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-badge-bg, --ren-badge-color, --ren-badge-font-size, --ren-badge-font-weight, --ren-badge-height, --ren-badge-padding-x, --ren-badge-radius."
    - "Semantic tokens: --color-fill, --color-fill-hover, --color-accent, --color-on-accent, --color-success-subtle / -strong, --color-warning-subtle / -strong, --color-danger-subtle / -strong, --color-info-subtle / -strong, --color-border."
    - "Layout tokens: --space-1, --radius-full, --stroke-1, --text-xs, --weight-semibold."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors for badge background, text, or dot color."
    - "Reaching past the semantic -subtle / -strong pair into raw success / warning / danger primitives."

accessibility:
  required:
    - "Badges that communicate status must not rely on color alone — include text or visually-hidden labels."
    - "Numeric badges need aria-label when the meaning is ambiguous (e.g., \"3 unread messages\")."
    - "Decorative dot badges (.ren-badge-dot) should be paired with adjacent text content — not used alone."
    - "Do not place a badge inside a focusable element without ensuring the badge itself is not announced twice; rely on the parent's accessible name."
    - "Maintain WCAG AA contrast: variants pair -subtle background with -strong text by design; do not invert."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-badge/ren-badge.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<span class="ren-badge ren-badge-success">Published</span>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-badge`
- `.ren-badge-danger`
- `.ren-badge-dot`
- `.ren-badge-dot-danger`
- `.ren-badge-dot-success`
- `.ren-badge-dot-warning`
- `.ren-badge-info`
- `.ren-badge-outline`
- `.ren-badge-primary`
- `.ren-badge-secondary`
- `.ren-badge-success`
- `.ren-badge-warning`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-badge-bg`
- `--ren-badge-color`
- `--ren-badge-font-size`
- `--ren-badge-font-weight`
- `--ren-badge-height`
- `--ren-badge-padding-x`
- `--ren-badge-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-badge/ren-badge.css`
- `docs/components/ren-badge.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-banner
type: contract
name: ren-banner component.md
path: components/primitives/ren-banner/component.md
data: {"kind":"primitive"}
body:
# ren-banner Component Contract

Persistent inline message for status, warning, or alert content.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-banner` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-banner` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Banner primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a persistent inline message that sits in page flow (success / warning / danger / info / neutral)."
    - "The message has structured content: icon + title + message + optional actions or dismiss button."
    - "You need a page-top full-bleed announcement via .ren-banner-full (no border-radius, no side borders)."
    - "You need a compact single-line variant for dense surfaces (.ren-banner-compact)."
    - "The banner should remain on screen until user dismisses it or the underlying state changes."
  avoidWhen:
    - "The message is transient and overlay-style — use ren-toast."
    - "The message blocks the entire flow and requires confirmation — use ren-dialog (alert)."
    - "You only need a small inline status label — use ren-badge."
    - "The message is form validation tied to a single field — use ren-field's error slot."

canonicalImports:
  css:
    - "rends/components/primitives/ren-banner/ren-banner.css"
  notes:
    - "CSS-only primitive — no colocated JS exists. Dismiss button is plain HTML; if you wire dismissal, set data-dismissing to trigger the documented animation."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Root must carry role=\"status\" (info / success / neutral) or role=\"alert\" (danger / urgent warning) so AT announces correctly."
  - "Use the documented children: <span class=\"ren-banner-icon\">, <div class=\"ren-banner-content\"> with <strong class=\"ren-banner-title\"> + <p class=\"ren-banner-message\">."
  - "Dismiss button is <button class=\"ren-banner-dismiss\" aria-label=\"Dismiss\"> — never a <div> with click handler."
  - "Wrap any banner action buttons in <div class=\"ren-banner-actions\"> placed inside .ren-banner-content."
  - "To animate dismissal, set [data-dismissing] on the root; do not animate via inline styles."

forbiddenPatterns:
  - "Using a banner as a transient toast that auto-dismisses without user action — wrong semantics."
  - "Hardcoded border / background colors per severity — use the .ren-banner-{success | warning | danger | neutral} variant."
  - "Stacking two severity variants on the same banner (e.g., .ren-banner-success.ren-banner-danger)."
  - "Dismiss button without aria-label or visible text — the ✕ glyph alone is not an accessible name."
  - "Communicating severity with icon color only; the role attribute and surrounding text must convey severity to AT."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-banner-bg, --ren-banner-border-color, --ren-banner-border-width, --ren-banner-color, --ren-banner-gap, --ren-banner-icon-size, --ren-banner-padding, --ren-banner-radius."
    - "Semantic tokens: --color-info-subtle, --color-success-subtle, --color-warning-subtle, --color-danger-subtle, --color-surface-sunken, --color-border, --color-text, --color-text-secondary, --color-text-muted, --color-focus-ring."
    - "Motion tokens: --duration-tactile, --duration-state, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors or hardcoded rgba border tints — use color-mix on semantic tokens as the CSS already does."
    - "Inline transition / animation values; use --duration-* / --ease-* tokens."

accessibility:
  required:
    - "Set role=\"alert\" for danger / urgent warning banners and role=\"status\" for non-urgent info / success / neutral."
    - "Dismiss button must have aria-label (or visible text) and a visible :focus-visible ring driven by --color-focus-ring."
    - ".ren-banner-title renders as a real heading-like element (<strong>); when stacked among <h*> content, follow document outline."
    - "Respect prefers-reduced-motion: the [data-dismissing] animation collapses to display:none per the CSS — do not re-introduce motion."
    - "Banner content must not rely on color alone; the icon + title + message convey severity together."
    - "Action buttons inside .ren-banner-actions must be real <button> elements with their own keyboard focus order."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-banner/ren-banner.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-banner ren-banner-info" role="status">
  <span class="ren-banner-icon" aria-hidden="true">i</span>
  <div class="ren-banner-content"><strong class="ren-banner-title">Maintenance</strong><p class="ren-banner-message">Service resumes at 11 PM.</p></div>
  <button class="ren-banner-dismiss" type="button" aria-label="Dismiss">×</button>
</div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-banner`
- `.ren-banner-actions`
- `.ren-banner-compact`
- `.ren-banner-content`
- `.ren-banner-danger`
- `.ren-banner-dismiss`
- `.ren-banner-full`
- `.ren-banner-icon`
- `.ren-banner-message`
- `.ren-banner-neutral`
- `.ren-banner-success`
- `.ren-banner-title`
- `.ren-banner-warning`

## States And Attributes

- `[data-dismissing]`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-banner-bg`
- `--ren-banner-border-color`
- `--ren-banner-border-width`
- `--ren-banner-color`
- `--ren-banner-gap`
- `--ren-banner-icon-size`
- `--ren-banner-padding`
- `--ren-banner-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-banner/ren-banner.css`
- `docs/components/ren-banner.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-breadcrumb
type: contract
name: ren-breadcrumb component.md
path: components/primitives/ren-breadcrumb/component.md
data: {"kind":"primitive"}
body:
# ren-breadcrumb Component Contract

Hierarchical navigation trail built on native navigation/list semantics.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-breadcrumb` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-breadcrumb` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Breadcrumb primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a hierarchical trail that shows the user's location within a site / app tree."
    - "The trail has 2+ ancestor levels and the final crumb represents the current page."
    - "You want native <nav> + <ol> semantics with the \"Breadcrumb\" landmark announcement."
    - "You need a customizable separator (default ›) via the --breadcrumb-separator custom property."
    - "Deep paths must collapse middle entries to an ellipsis via .ren-breadcrumb-truncated."
  avoidWhen:
    - "The navigation is the primary site nav — use ren-nav."
    - "The navigation is a tabbed page section — use ren-tabs."
    - "The navigation is a multi-step wizard — use ren-steps / ren-progress."
    - "You only need a single \"Back\" link — use ren-link with an arrow icon."

canonicalImports:
  css:
    - "rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css"
  notes:
    - "CSS-only primitive — no colocated JS exists. Do not import a ren-breadcrumb.js."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Root must be <nav aria-label=\"Breadcrumb\" class=\"ren-breadcrumb\"> — the aria-label is what AT announces."
  - "Items are wrapped in an ordered list: <ol> with <li> children; do not use <ul> or plain <div>s."
  - "Each crumb is an <a href=\"...\"> except the current page, which uses aria-current=\"page\" and may stay as <a> (pointer-events disabled by CSS) or render as plain text."
  - "Separators are CSS-generated via ::before — never insert literal \"/\" or \"›\" characters as text nodes."
  - "Override the separator per-instance with inline style=\"--breadcrumb-separator: '/'\" — do not edit the CSS."

forbiddenPatterns:
  - "Using <div class=\"ren-breadcrumb\"> without <nav> — strips the landmark."
  - "Marking multiple crumbs with aria-current=\"page\" — only the final crumb is current."
  - "Inserting separator glyphs as text content inside <li> — the ::before pseudo handles it."
  - "Hardcoded font-size or color overrides via inline style — use the --ren-breadcrumb-* tokens."
  - "Wrapping crumb links in ren-link / ren-btn — breadcrumb owns its own link chrome."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-breadcrumb-active-color, --ren-breadcrumb-color, --ren-breadcrumb-font-size, --ren-breadcrumb-gap, --ren-breadcrumb-separator."
    - "Semantic tokens: --color-text, --color-text-muted, --color-text-faint, --color-focus-ring."
    - "Layout / motion tokens: --space-1, --radius-sm, --duration-tactile, --ease-enter, --ring-width, --caption-size, --text-sm."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors for link / current / separator styling."
    - "Overriding text-decoration with !important — let :hover and aria-current rules drive it."

accessibility:
  required:
    - "Root <nav> must carry aria-label=\"Breadcrumb\" (translated per locale) so AT announces the landmark."
    - "Current page crumb uses aria-current=\"page\"; CSS disables pointer-events but the semantic state is what matters for AT."
    - "Crumb links must have visible :focus-visible outline driven by --color-focus-ring and --ring-width."
    - "Separators are decorative (CSS ::before) so they are not announced — never put separator text in the DOM."
    - "When using .ren-breadcrumb-truncated, the hidden middle crumbs are display:none — ensure the ellipsis is purely decorative and provide a way to expand if the hidden context matters."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-breadcrumb/ren-breadcrumb.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<nav class="ren-breadcrumb" aria-label="Breadcrumb"><ol><li><a href="/">Home</a></li><li aria-current="page">Settings</li></ol></nav>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-breadcrumb`
- `.ren-breadcrumb-truncated`

## States And Attributes

- `[aria-current]`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-breadcrumb-active-color`
- `--ren-breadcrumb-color`
- `--ren-breadcrumb-font-size`
- `--ren-breadcrumb-gap`
- `--ren-breadcrumb-separator`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-breadcrumb/ren-breadcrumb.css`
- `docs/components/ren-breadcrumb.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-button
type: contract
name: ren-button component.md
path: components/primitives/ren-button/component.md
data: {"kind":"primitive"}
body:
# ren-button

Action primitive for commands, form submissions, links styled as controls,
icon-only triggers, and loading states.

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `ren-btn` / `<ren-button>` UI.

## Purpose

The single touch-target primitive in RenDS. Owns visible action affordances:
solid CTAs, secondary commands, ghosted toolbar actions, link-styled inline
actions, and icon-only buttons. Does not own form inputs, navigation, or
dialog launchers — those compose `ren-button` inside their own contracts.

## Use When

- The element is a clickable command: submit, save, delete, open modal,
  toggle, fire any imperative action.
- An anchor needs to *look* like a button (use `.ren-btn` on `<a>` only when
  the link semantically replaces a button — otherwise use `ren-link`).
- A trigger is part of a composite (`ren-dialog` opener, `ren-menu` trigger,
  `ren-popover` anchor); the inner element should be `<button class="ren-btn">`.
- A loading / busy state must be reflected in markup (`data-loading`).

## Do Not Use When

- The control is a form input (use `ren-field` + native input).
- The control is plain navigation (use `ren-link` or a real `<a>`).
- The control is a checkbox / radio / toggle (use `ren-checkbox`,
  `ren-radio`, `ren-toggle-group`).
- The element is a styled label or status (use `ren-badge` or `ren-tag`).

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "User action triggers an imperative side effect (submit, save, delete, open dialog)."
    - "Need primary / secondary / destructive emphasis tied to the same touch-target shape."
    - "Need a 44px-minimum touch target with a visible focus ring."
    - "Need a loading / busy affordance via data-loading + aria-busy."
    - "Need an icon-only control with an accessible name (.ren-btn-icon + aria-label)."
  avoidWhen:
    - "The element is purely navigational — use ren-link."
    - "The element is a form input — use ren-field with a native input."
    - "The element is a stateful selector (checkbox, radio, switch, toggle)."
    - "You only need text emphasis or a status pill — use ren-badge / ren-tag."

canonicalImports:
  css:
    - "rends/components/primitives/ren-button/ren-button.css"
  js:
    - "rends/components/primitives/ren-button/ren-button.js"
  notes:
    - "If the page already imports rends/components/index.css, do not import the CSS again."
    - "JS is only required when using <ren-button> attributes (loading, variant, size, icon, full); plain <button class=\"ren-btn\"> works without JS."

requiredMarkup:
  - "Always render an actual <button> (or <ren-button>) element. Never style a <div> as a button."
  - "Icon-only buttons must include an accessible name via aria-label or visually-hidden text."
  - "Loading buttons must set data-loading and aria-busy=\"true\" on the same node."
  - "Use type=\"button\" for non-submit buttons inside <form> to avoid accidental submission."

forbiddenPatterns:
  - "div / span with a click handler styled as a button."
  - "<button style=\"background: #...\"> — use --ren-btn-bg."
  - "Removing the focus ring (outline: none) without restoring a visible :focus-visible style."
  - "Custom danger styling that overrides --color-danger with a primitive --red-* token."
  - "Disabling via aria-disabled alone without preventing the click handler."

tokenPolicy:
  allowed:
    - "Semantic tokens: --color-accent, --color-on-accent, --color-danger, --color-text-link, --color-focus-ring."
    - "Component tokens: every --ren-btn-* listed in Public Token API."
    - "Layout / size tokens: --space-*, --size-*, --radius-*."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, etc.) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Raw transition values; use --ren-btn-duration / --ren-btn-easing."

accessibility:
  required:
    - "Real <button> semantics; rely on native click / Enter / Space."
    - "Visible :focus-visible ring driven by --ren-btn-ring-* tokens."
    - "Touch target ≥ 44px (default size). Smaller (.ren-btn-sm = 32px) only for non-touch contexts."
    - "Icon-only buttons have aria-label or visually-hidden label text."
    - "Loading buttons expose aria-busy=\"true\" and ignore activation while busy."
    - "Disabled buttons set both `disabled` (real) or `aria-disabled=\"true\"` AND prevent activation in the handler."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-button/ren-button.css">
<script type="module" src="rends/components/primitives/ren-button/ren-button.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the
CSS again. JS is only required when consuming `<ren-button>` attributes.

## Canonical Markup

Primary action button:

```html
<button class="ren-btn">Save changes</button>
```

Secondary + small:

```html
<button class="ren-btn ren-btn-secondary ren-btn-sm" type="button">Cancel</button>
```

Destructive with loading state:

```html
<button class="ren-btn ren-btn-danger" data-loading aria-busy="true" disabled>
  Deleting...
</button>
```

Icon-only with accessible name:

```html
<button class="ren-btn ren-btn-icon" aria-label="Close dialog">
  <svg aria-hidden="true" focusable="false" width="16" height="16">...</svg>
</button>
```

Web component form (attribute-driven state):

```html
<ren-button variant="secondary" size="sm">Cancel</ren-button>
<ren-button variant="primary" loading>Submitting</ren-button>
```

## Variants

| Class                  | Role                                |
|------------------------|-------------------------------------|
| `.ren-btn`             | Default primary CTA.                |
| `.ren-btn-primary`     | Same as default; explicit.          |
| `.ren-btn-secondary`   | Tonal / fill-style secondary.       |
| `.ren-btn-outline`     | Border-only, transparent fill.      |
| `.ren-btn-ghost`       | No fill, no border; text + hover.   |
| `.ren-btn-link`        | Inline text-style action.           |
| `.ren-btn-danger`      | Destructive emphasis.               |
| `.ren-btn-accent`      | Custom accent via `--btn-accent`.   |
| `.ren-btn-icon`        | Square icon-only target.            |
| `.ren-btn-full`        | Stretches to container width.       |
| `.ren-btn-group`       | Joined sibling buttons.             |
| `.ren-btn-sm` / `-lg`  | Size override (32px / 52px).        |

## States

| Selector / attr        | Meaning                                                |
|------------------------|--------------------------------------------------------|
| `:hover`               | Pointer is over an interactive button.                 |
| `:active`              | Pressed state.                                         |
| `:focus-visible`       | Keyboard focus; renders the ring.                      |
| `:disabled`            | Native disabled; non-interactive, in tab order off.    |
| `[aria-disabled="true"]` | ARIA-disabled; visually disabled but stays focusable.|
| `[data-loading]`       | Spinner replaces label; pair with `aria-busy="true"`.  |

## Public Token API

Override these on `:root`, `[data-theme]`, or a scope wrapping the button.
Do **not** override the internal `.ren-btn` class.

- `--ren-btn-bg`
- `--ren-btn-bg-hover`
- `--ren-btn-bg-active`
- `--ren-btn-color`
- `--ren-btn-border-color`
- `--ren-btn-border-width`
- `--ren-btn-radius`
- `--ren-btn-height`
- `--ren-btn-padding-x`
- `--ren-btn-padding-y`
- `--ren-btn-gap`
- `--ren-btn-font-size`
- `--ren-btn-font-weight`
- `--ren-btn-ring-width`
- `--ren-btn-ring-offset`
- `--ren-btn-ring-color`
- `--ren-btn-duration`
- `--ren-btn-easing`

## Accessibility Contract

- Use a real `<button>` (or `<ren-button>` which renders a button internally).
- Keep a visible focus ring driven by `--ren-btn-ring-*`.
- Maintain a 44px minimum touch target (default size). `ren-btn-sm` (32px)
  is reserved for non-touch contexts (toolbars, dense desktop UI).
- Icon-only buttons must have an accessible name via `aria-label` or visually
  hidden text inside the button.
- Loading buttons expose `aria-busy="true"`. The component sets it
  automatically when `loading` is the chosen attribute on `<ren-button>`.
- Do not communicate state through color alone — `ren-btn-danger` carries the
  variant in the class, not just the hue.

## Anti-Patterns

- ❌ `<div class="ren-btn" onclick="...">` — must be a real `<button>`.
- ❌ Inline hex colors: `<button style="background:#0066ff;">`.
- ❌ Overriding `.ren-btn { background: ... !important }` — use
  `--ren-btn-bg` on a parent scope.
- ❌ Mixing `disabled` and a click handler that still fires (the handler
  must early-return or be detached).
- ❌ Using `ren-btn-link` when the destination is a navigation URL — use
  `ren-link` instead.

## Related Files

- `components/primitives/ren-button/ren-button.css`
- `components/primitives/ren-button/ren-button.js`
- `docs/components/ren-button.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, ARIA, or states change.
- Run `npm run lint` after token / selector changes.
- Manually verify light/dark themes when surface, border, or shadow values
  change — focus rings must remain visible in both modes.


id: contract:primitive:ren-card
type: contract
name: ren-card component.md
path: components/primitives/ren-card/component.md
data: {"kind":"primitive"}
body:
# ren-card

Container primitive for grouped content with header, body, footer, status,
and interactive (clickable / selectable) variants.

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `.ren-card` UI.

## Purpose

Owns the shape, padding, surface, and border of grouped content. Provides
slots (`-header`, `-body`, `-footer`) and a small set of variants
(`-elevated`, `-outline`, `-sunken`, `-ghost`, `-simple`,
`-interactive`, `-selectable`) plus a status hook (`[data-status]`).
Card never owns inputs, dialogs, or navigation behavior — it is a passive
container.

## Use When

- A group of related content needs its own surface separated from the page.
- A list of repeating items would benefit from a consistent card grid
  (`ren-grid` + `ren-card`).
- A tile must be clickable or selectable as a whole (use
  `.ren-card-interactive` or `.ren-card-selectable`).
- A surface needs a header / body / footer layout already proven by RenDS.

## Do Not Use When

- The container is just a layout primitive — use `ren-stack`, `ren-cluster`,
  `ren-grid`, etc.
- The container is a modal — use `ren-dialog`.
- The container is a popover / tooltip / menu — use those composites.
- You need an alert / banner / call-out — use `ren-banner`.
- You need a form section — use `ren-form` + `ren-field` directly.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You want a visually grouped surface (border / shadow / radius) with predictable spacing."
    - "You need a card grid (stat tiles, product cards, list items)."
    - "You need a whole-card click target (.ren-card-interactive) or selection (.ren-card-selectable)."
    - "You need a status accent driven by [data-status]."
  avoidWhen:
    - "You only need vertical spacing — use ren-stack."
    - "You need overlay behavior — use ren-dialog / ren-popover / ren-sheet."
    - "You need a banner / alert message — use ren-banner."
    - "You need a navigation list with hover / active — use ren-sidebar items."

canonicalImports:
  css:
    - "rends/components/primitives/ren-card/ren-card.css"
  notes:
    - "Card is CSS-only. There is no <ren-card> custom element."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "A single .ren-card root wrapping any number of .ren-card-header, .ren-card-body, .ren-card-footer."
  - ".ren-card-title is the heading; use a real <h*> element inside it for outline / a11y."
  - "Selectable cards must wire aria-selected (or [data-selected]) AND a keyboard handler — the class only styles."
  - "Interactive cards (whole-card click) should wrap content in a real <a> or <button>, not a div with onclick."

forbiddenPatterns:
  - "Nesting another .ren-card inside .ren-card-body — flatten the structure."
  - "Replacing internal .ren-card-* selectors with custom classes for theming — use --ren-card-* tokens."
  - "Hardcoded shadow / radius values overriding card defaults; theme through tokens."

tokenPolicy:
  allowed:
    - "Component tokens: every --ren-card-* listed in Public Token API."
    - "Semantic tokens for content inside the card (--color-text, --color-text-muted, etc.)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code."
    - "Hardcoded hex / rgb colors in consumer overrides."

accessibility:
  required:
    - "Card titles use real heading elements (<h2>, <h3>) — never style a <div> as a heading."
    - "Whole-card click targets must use <a> or <button> wrappers and respect tab order / focus rings."
    - "Selectable cards announce state via aria-selected + data-selected; pair with keyboard activation."
    - "[data-status] is decorative; pair it with meaningful text or icons, not color alone."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-card/ren-card.css">
<!-- No JS — card is CSS-only. -->
```

## Canonical Markup

Static card with header + body:

```html
<article class="ren-card">
  <div class="ren-card-header">
    <h3 class="ren-card-title">Project status</h3>
    <p class="ren-card-description">Updated 2 minutes ago.</p>
  </div>
  <div class="ren-card-body">
    <p>All checks passing.</p>
  </div>
</article>
```

Interactive (whole-card click) with link wrapper:

```html
<a class="ren-card ren-card-interactive" href="/projects/atlas">
  <div class="ren-card-header">
    <h3 class="ren-card-title">Atlas</h3>
  </div>
  <div class="ren-card-body">42 open issues</div>
</a>
```

Selectable in a grid:

```html
<div class="ren-grid-3">
  <button class="ren-card ren-card-selectable" type="button" aria-pressed="false">
    <span class="ren-card-title">Hobby</span>
    <span class="ren-card-description">$0/mo</span>
  </button>
</div>
```

## Variants

| Class                   | Role                                      |
|-------------------------|-------------------------------------------|
| `.ren-card`             | Default surface.                          |
| `.ren-card-elevated`    | Adds shadow.                              |
| `.ren-card-outline`     | Border-only, transparent fill.            |
| `.ren-card-sunken`      | Inset surface (sunken background).        |
| `.ren-card-ghost`       | No surface; spacing only.                 |
| `.ren-card-simple`      | Reduced padding for dense lists.          |
| `.ren-card-interactive` | Hover / focus styles for clickable cards. |
| `.ren-card-selectable`  | Toggleable card; pair with `aria-selected`. |
| `.ren-card-cover`       | Edge-to-edge media slot at the top.       |
| `.ren-card-group`       | Joined sibling cards with shared borders. |
| `.ren-card-footer-border` | Footer with top divider.                |

## States

| Selector / attr        | Meaning                                     |
|------------------------|---------------------------------------------|
| `:hover` / `:active`   | Pointer interaction (interactive cards).    |
| `:focus-visible`       | Keyboard focus ring (interactive cards).    |
| `[aria-selected="true"]` / `[data-selected]` | Selection state.       |
| `[data-status="success" / "warning" / "danger" / "info"]` | Status accent. |

## Public Token API

- `--ren-card-bg`
- `--ren-card-border-color`
- `--ren-card-border-width`
- `--ren-card-radius`
- `--ren-card-padding`
- `--ren-card-gap`
- `--ren-card-header-gap`
- `--ren-card-shadow`

## Accessibility Contract

- Card titles are real `<h*>` elements; never style a `<div>` as a heading.
- Whole-card clickability wraps the entire card in a real `<a>` or `<button>`.
- Selectable cards must expose `aria-selected` (or `aria-pressed`) plus a
  visible focus ring via `:focus-visible`.
- `[data-status]` is decorative; status meaning must be carried by text or
  icons too.

## Anti-Patterns

- ❌ `<div class="ren-card" onclick="...">` — wrap with a real link / button.
- ❌ Nesting `.ren-card` inside `.ren-card-body`.
- ❌ Inline hex colors on `.ren-card { background: #FFFFFF; }` — override
  `--ren-card-bg` on the parent scope.
- ❌ Replacing built-in selectors (`.ren-card-title`) with custom classes
  for theming — theme through component tokens.

## Related Files

- `components/primitives/ren-card/ren-card.css`
- `docs/components/ren-card.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, ARIA, or states change.
- Run `npm run lint` after token / selector changes.
- Manually verify light/dark themes when surface, border, or shadow values
  change.


id: contract:primitive:ren-checkbox
type: contract
name: ren-checkbox component.md
path: components/primitives/ren-checkbox/component.md
data: {"kind":"primitive"}
body:
# ren-checkbox Component Contract

Native checkbox styling with checked, indeterminate, disabled, and focus states.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-checkbox` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-checkbox` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Checkbox primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The control toggles a boolean value (checked / unchecked) for a single option."
    - "The control belongs in a list where each option is independently selectable (multi-select)."
    - "You need an indeterminate / mixed state (e.g., parent of a tree, \"select all\" with partial children)."
    - "You need native form-submission semantics (name / value / required) tied to a real <input type=\"checkbox\">."
    - "The label sits next to a custom-styled box that participates in :checked / :hover / :focus-visible / :indeterminate / :disabled states."
  avoidWhen:
    - "The control is an on/off toggle with two visible states — use ren-switch."
    - "The control is one of several mutually-exclusive options — use ren-radio."
    - "You need a yes/no choice inside a tabular row without form submission — consider ren-switch."
    - "You need a tri-state with named states beyond on/off/mixed — design a custom segmented control."

canonicalImports:
  css:
    - "rends/components/primitives/ren-checkbox/ren-checkbox.css"
  notes:
    - "CSS-only primitive — no colocated JS exists. Indeterminate must be set in JS via input.indeterminate = true; the CSS reacts to :indeterminate."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Wrap each control in <label class=\"ren-checkbox\"> containing exactly one <input type=\"checkbox\"> and one <span class=\"ren-checkbox-control\"> sibling, in that order."
  - "Label text goes in a trailing <span> (or text node) after .ren-checkbox-control so :checked + .ren-checkbox-control adjacency works."
  - "Group related checkboxes inside a <fieldset> with a <legend> to provide a group accessible name."
  - "For indeterminate state, set input.indeterminate = true via JS; you cannot express it via an HTML attribute alone."
  - "Disabled inputs must use the real disabled attribute — the .ren-checkbox:has(input:disabled) selector relies on it."

forbiddenPatterns:
  - "Replacing <input type=\"checkbox\"> with a <div role=\"checkbox\"> — breaks form submission and native keyboard."
  - "Hiding the input with display:none — destroys keyboard focus; use the documented visually-hidden technique already in the CSS."
  - "Custom checkmark glyphs injected via JS or inline SVG — the ::after pseudo owns the checkmark."
  - "Mixing two label nodes per input (e.g., wrapping <label> plus a separate <label for>) — choose one."
  - "Communicating error / validity via color alone; pair with text or an inline error message."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-checkbox-border, --ren-checkbox-checked-bg, --ren-checkbox-checked-color, --ren-checkbox-duration, --ren-checkbox-easing, --ren-checkbox-gap, --ren-checkbox-radius, --ren-checkbox-size."
    - "Semantic tokens: --color-accent, --color-accent-hover, --color-on-accent, --color-border-strong, --color-text, --color-focus-ring."
    - "Layout / motion tokens: --space-2, --radius-sm, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors for the checked fill, border, or checkmark."
    - "Raw transition values; use --ren-checkbox-duration / --ren-checkbox-easing or the --duration-* / --ease-* tokens."

accessibility:
  required:
    - "Always use a real <input type=\"checkbox\"> so Space toggles, Tab focuses, and form submission carries the value."
    - "Touch target stays ≥ var(--touch-min) via min-height on .ren-checkbox; do not shrink below that for touch contexts."
    - "Focus is visible on the .ren-checkbox-control via :focus-visible outline driven by --color-focus-ring and --ring-width."
    - "Each checkbox has an accessible name via its wrapping <label> text; never rely on placeholder or title alone."
    - "Group multiple related checkboxes inside <fieldset><legend> for a programmatic group name."
    - "Disabled state must use the native disabled attribute (not just aria-disabled) so the input is excluded from form submission and keyboard navigation."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-checkbox/ren-checkbox.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<label class="ren-checkbox">
  <input type="checkbox" name="updates">
  <span class="ren-checkbox-control"></span>
  <span>Email updates</span>
</label>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-checkbox`
- `.ren-checkbox-control`

For toggle switches with on/off semantics, see the sibling primitive `ren-switch`.

## States And Attributes

- `:active`
- `:checked`
- `:disabled`
- `:focus-visible`
- `:hover`
- `:indeterminate`

## Public Token API

- `--ren-checkbox-border`
- `--ren-checkbox-checked-bg`
- `--ren-checkbox-checked-color`
- `--ren-checkbox-duration`
- `--ren-checkbox-easing`
- `--ren-checkbox-gap`
- `--ren-checkbox-radius`
- `--ren-checkbox-size`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-checkbox/ren-checkbox.css`
- `docs/components/ren-checkbox.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-field
type: contract
name: ren-field component.md
path: components/primitives/ren-field/component.md
data: {"kind":"primitive"}
body:
# ren-field

Form-field wrapper that connects labels, descriptions, errors, and the
underlying control through ARIA. Carries the field's visual frame
(input chrome, error / valid borders, sizing).

Load this file after `ren-design.md` and before generating, editing, or
reviewing any `<ren-field>` UI.

## Purpose

Owns the wiring between a `<label>`, helper text (`[data-description]`),
error text (`[data-error]`), and the control inside it. The custom element
auto-generates IDs, links them via `for` / `aria-labelledby` /
`aria-describedby` / `aria-errormessage`, and toggles `aria-invalid` and
`data-invalid` from the host's validation state. Visual styling for the
control itself comes from `.ren-input` / `.ren-input-wrapper`.

## Use When

- The UI is a labeled form control: text, email, password, number, search,
  url, date, textarea, select.
- The control needs helper text and / or error text wired through ARIA.
- You want consistent field heights, border / focus ring treatment, and
  validation states across the app.
- The field belongs to a `ren-form` pattern that drives validation.

## Do Not Use When

- The control is a single button — use `ren-button`.
- The control is a checkbox / radio / switch — use `ren-checkbox` /
  `ren-radio` / `ren-toggle-group`.
- The control is a search/combobox with autocomplete — use `ren-combobox`.
- You only need a static label-value pair for display — use `<dl>` /
  `ren-card` content.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The control is a labeled form input that benefits from auto-wired ARIA."
    - "You need helper text + error text + invalid styling tied to the same control."
    - "You want a consistent input frame (height, padding, focus ring, error border)."
    - "You are inside a <ren-form> that drives validation."
  avoidWhen:
    - "The control is a checkbox / radio / switch — use those primitives directly."
    - "The control is a combobox / select with custom listbox — use ren-combobox / ren-select."
    - "You need a fully custom number stepper — use ren-number-field."
    - "You need OTP segmented inputs — use ren-otp."

canonicalImports:
  css:
    - "rends/components/primitives/ren-field/ren-field.css"
  js:
    - "rends/components/primitives/ren-field/ren-field.js"
  notes:
    - "JS is required for ARIA wiring and validation state syncing — do not skip."

requiredMarkup:
  - "<ren-field> wraps exactly one <label>, exactly one control, plus optional [data-description] and [data-error]."
  - "Error and description nodes do not need their own IDs — the component generates them."
  - "Native control attributes drive validity (required, type=email, pattern, min, max). Do not duplicate validation in JS for the same rules."
  - "Use .ren-input on inputs / textareas / selects; use .ren-input-wrapper for icon adornments."

forbiddenPatterns:
  - "Manual aria-describedby / aria-errormessage on the input — the component sets them."
  - "Reaching into the slotted control to override .ren-input internals; theme via --ren-field-* tokens."
  - "Showing error text without setting [data-invalid] / aria-invalid on the control."
  - "Hiding required-state info in placeholder text."

tokenPolicy:
  allowed:
    - "Component tokens: every --ren-field-* listed in Public Token API."
    - "Semantic tokens for any custom decoration (--color-text-muted for the description, etc.)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, etc.) in consumer code."
    - "Hardcoded hex / rgb colors in consumer overrides."

accessibility:
  required:
    - "Always provide a visible <label>; do not rely on placeholder text."
    - "Required inputs must mark required (the attribute) — visual indicator alone is not enough."
    - "Error messages must be set in [data-error] AND aria-invalid must be true on the control."
    - "Keep visible focus rings; do not remove the input outline without a replacement."
    - "Disabled fields use the disabled attribute on the control, not aria-disabled alone."
```

## Required CSS / JS Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-field/ren-field.css">
<script type="module" src="rends/components/primitives/ren-field/ren-field.js"></script>
```

## Canonical Markup

Required email field with helper + error:

```html
<ren-field>
  <label>Email</label>
  <input class="ren-input" type="email" name="email" autocomplete="email" required>
  <span data-description>We will never share your email.</span>
  <span data-error>Enter a valid email address.</span>
</ren-field>
```

Password with visible label and helper:

```html
<ren-field>
  <label>Password</label>
  <input class="ren-input" type="password" name="password" autocomplete="new-password" minlength="8" required>
  <span data-description>At least 8 characters.</span>
  <span data-error></span>
</ren-field>
```

Textarea:

```html
<ren-field>
  <label>Notes</label>
  <textarea class="ren-input" name="notes" rows="4"></textarea>
</ren-field>
```

Input with leading icon:

```html
<ren-field>
  <label>Search</label>
  <span class="ren-input-wrapper">
    <span class="ren-input-icon" aria-hidden="true">🔍</span>
    <input class="ren-input" type="search" name="q">
  </span>
</ren-field>
```

## Variants

| Class                       | Role                                |
|-----------------------------|-------------------------------------|
| `.ren-input`                | Base input chrome.                  |
| `.ren-input-sm` / `-lg`     | Size override.                      |
| `.ren-input-error`          | Manual error styling on the input.  |
| `.ren-input-success`        | Manual success styling.             |
| `.ren-input-wrapper`        | Container for adornments.           |
| `.ren-input-icon`           | Leading icon slot.                  |
| `.ren-input-icon-end`       | Trailing icon slot.                 |
| `.ren-field-label`          | Label slot styling.                 |
| `.ren-field-description`    | Helper text slot styling.           |
| `.ren-field-error`          | Error slot styling.                 |

## States

| Selector / attr        | Meaning                                              |
|------------------------|------------------------------------------------------|
| `:hover` / `:active`   | Pointer interaction.                                 |
| `:focus-visible`       | Keyboard focus on the control.                       |
| `:disabled`            | Native disabled state.                               |
| `:user-invalid`        | Native validity after interaction (CSS Lv4).         |
| `:user-valid`          | Native validity after interaction (CSS Lv4).         |
| `[aria-invalid="true"]` / `[data-invalid]` | Component-driven invalid state.  |
| `[data-valid]`         | Component-driven valid state.                        |
| `[data-required]`      | Component marks required visually.                   |
| `[data-description]`   | Slot for helper text.                                |
| `[data-error]`         | Slot for error text.                                 |

## Public Token API

- `--ren-field-bg`
- `--ren-field-border-color`
- `--ren-field-border-width`
- `--ren-field-radius`
- `--ren-field-height`
- `--ren-field-padding-x`
- `--ren-field-padding-y`
- `--ren-field-font-size`
- `--ren-field-color`
- `--ren-field-placeholder`
- `--ren-field-focus-color`
- `--ren-field-error-color`
- `--ren-field-success-color`

## Accessibility Contract

- Always provide a visible `<label>`; placeholder is not a label.
- Required inputs use the native `required` attribute; mark it visually too.
- Error text lives in `[data-error]`. The component sets `aria-invalid` on
  the control automatically when the host marks the field invalid.
- Focus rings must remain visible — do not strip outline without restoring.
- Disabled fields use `disabled` on the control. `aria-disabled` is only
  acceptable when the disabled state must remain focusable.

## Anti-Patterns

- ❌ Putting label text in `placeholder` instead of `<label>`.
- ❌ Manual `aria-describedby` / `aria-errormessage` on the input — the
  component owns these.
- ❌ Showing error text without setting `aria-invalid` on the control.
- ❌ `.ren-input { border: 2px solid #ccc; }` — theme through
  `--ren-field-border-*` tokens.

## Related Files

- `components/primitives/ren-field/ren-field.css`
- `components/primitives/ren-field/ren-field.js`
- `components/patterns/ren-form/pattern.md` (validation pattern)
- `docs/components/ren-field.html`
- `ren-design.md`
- `tokens/tokens.md`

## Test Expectations

- Run component / docs a11y coverage when markup, ARIA, or validation
  behavior changes.
- Run `npm run lint` after token / selector changes.
- Manually verify light/dark themes for focus ring and error glow.


id: contract:primitive:ren-icon
type: contract
name: ren-icon component.md
path: components/primitives/ren-icon/component.md
data: {"kind":"primitive"}
body:
# ren-icon Component Contract

Icon sizing, color, and motion utility for SVG icons.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-icon` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-icon` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Icon primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a consistent sizing wrapper (xs/sm/md/lg/xl/2xl) around an inline SVG."
    - "You need an icon that inherits the parent text color via currentColor."
    - "You need a semantic color tint (primary, success, warning, danger, muted) on a glyph."
    - "You need a spinning loading glyph that respects prefers-reduced-motion."
    - "You need an icon slot inside ren-button, ren-link, ren-badge, or other primitives."
  avoidWhen:
    - "The graphic is decorative and inline — a raw <svg aria-hidden=\"true\"> is enough."
    - "The element is a clickable control — wrap the icon inside ren-button (ren-btn-icon) instead."
    - "You need a multi-color illustration or logo — use a plain <svg> with its own viewport."

canonicalImports:
  css:
    - "rends/components/primitives/ren-icon/ren-icon.css"
  notes:
    - "CSS-only primitive; no JavaScript file exists for ren-icon."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Always wrap a real <svg> (or icon-font glyph) inside <span class=\"ren-icon\"> — never style the SVG directly with .ren-icon."
  - "Decorative icons inside text or buttons must set aria-hidden=\"true\" on the SVG (the wrapper has no role)."
  - "Standalone meaningful icons must have an accessible name on the parent control (aria-label) or visually-hidden text — the .ren-icon wrapper itself does not carry a name."
  - "Pick exactly one size variant (.ren-icon-xs through .ren-icon-2xl); the base .ren-icon defaults to md (20px)."
  - ".ren-icon-spin must be paired with role=\"status\" or aria-busy on a parent for screen reader users."

forbiddenPatterns:
  - "Setting width/height inline on <svg> instead of using the .ren-icon-* size variants."
  - "Hardcoding fill=\"#...\" on the inner SVG — drive color via currentColor and the .ren-icon-* color variants."
  - "Using .ren-icon-danger to convey state without an accompanying text label (state by color alone)."
  - "Animating the spin with a custom CSS animation instead of .ren-icon-spin (loses reduced-motion guard)."
  - "Using .ren-icon as a clickable target — wrap with a real <button> or use ren-button instead."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-icon-color, --ren-icon-fill, --ren-icon-size."
    - "Semantic tokens consumed internally: --color-accent, --color-success, --color-warning, --color-danger, --color-text-secondary."
    - "Motion tokens: --duration-loop, --ease-loop (for .ren-icon-spin)."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex or named colors on the SVG or wrapper."
    - "Raw animation durations; rely on --duration-loop / --ease-loop via .ren-icon-spin."

accessibility:
  required:
    - "Decorative icons set aria-hidden=\"true\" on the SVG element."
    - "Meaningful icons get their accessible name from the surrounding control (button aria-label, link text, etc.) — never from the .ren-icon wrapper alone."
    - "Color variants (.ren-icon-success, .ren-icon-danger) must be paired with text or aria-label so meaning is not color-only."
    - ".ren-icon-spin animation honors prefers-reduced-motion: reduce automatically."
    - "Do not turn .ren-icon into an interactive target; it has no focus styles or hit-area guarantees."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-icon/ren-icon.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<span class="ren-icon" aria-hidden="true">
  <svg viewBox="0 0 24 24" focusable="false"><path d="M5 12h14"></path></svg>
</span>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-icon`
- `.ren-icon-2xl`
- `.ren-icon-danger`
- `.ren-icon-lg`
- `.ren-icon-md`
- `.ren-icon-muted`
- `.ren-icon-primary`
- `.ren-icon-sm`
- `.ren-icon-spin`
- `.ren-icon-success`
- `.ren-icon-warning`
- `.ren-icon-xl`
- `.ren-icon-xs`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-icon-color`
- `--ren-icon-fill`
- `--ren-icon-size`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-icon/ren-icon.css`
- `docs/components/ren-icon.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-kbd
type: contract
name: ren-kbd component.md
path: components/primitives/ren-kbd/component.md
data: {"kind":"primitive"}
body:
# ren-kbd Component Contract

Keyboard key / shortcut primitive for documentation and command hints.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-kbd` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-kbd` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Kbd primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need to display a single keyboard key (⌘, K, Esc, Enter) in documentation or command hints."
    - "You need to render a keyboard combo (⌘ + K) where each key is a chip with native <kbd> semantics."
    - "You want monospaced, chip-styled tokens with a sunken background and bottom-shadow border."
    - "You need an inline label inside menu items, tooltips, or docs showing the shortcut for an action."
  avoidWhen:
    - "The element is a status pill or category label — use ren-badge or ren-tag."
    - "The element is a clickable shortcut trigger — use ren-button and place a <kbd> inside its label."
    - "You need block-level code samples — use <pre><code> or a code-block component, not <kbd>."

canonicalImports:
  css:
    - "rends/components/primitives/ren-kbd/ren-kbd.css"
  notes:
    - "CSS-only primitive; no JavaScript file exists for ren-kbd."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Always use a real <kbd> element with class=\"ren-kbd\"; never style a <span> or <code> as a kbd chip."
  - "Each key in a combo is its own <kbd class=\"ren-kbd\">; the joiner (+) lives between them as plain text."
  - "Inside running prose, place the <kbd> inline (it is display: inline-flex with no forced line break)."
  - "Use white-space: nowrap behavior built in — do not wrap the key glyph across lines via extra markup."

forbiddenPatterns:
  - "<span class=\"ren-kbd\"> or <code class=\"ren-kbd\"> — must be a real <kbd> for assistive tech."
  - "Hardcoded background or border colors that bypass --ren-kbd-bg / --ren-kbd-border."
  - "Adding click handlers on .ren-kbd to fire a shortcut — wrap the kbd inside a ren-button instead."
  - "Using <kbd class=\"ren-kbd\"> for a multi-character command name (e.g., \"git status\") — use <code> for that."
  - "Stacking multiple keys inside a single <kbd> (e.g., <kbd>⌘K</kbd>) — split them into one <kbd> per key."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-kbd-bg, --ren-kbd-border, --ren-kbd-color, --ren-kbd-font-family, --ren-kbd-font-size, --ren-kbd-padding, --ren-kbd-radius."
    - "Semantic tokens consumed internally: --color-text, --color-surface-sunken, --color-border-strong."
    - "Type tokens: --font-mono, --caption-size, --weight-medium."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Custom font-family overrides that bypass --ren-kbd-font-family / --font-mono."

accessibility:
  required:
    - "Use the native <kbd> element so screen readers announce \"keyboard input\" semantics."
    - "Keep contrast on the chip background ≥ WCAG AA against surrounding text (driven by --color-surface-sunken / --color-text)."
    - "Do not rely on the chip styling alone to convey shortcut meaning; pair with descriptive text (e.g., \"Press <kbd>⌘</kbd> + <kbd>K</kbd> to open search\")."
    - "Do not make .ren-kbd interactive — it has no focus ring or hit target."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-kbd/ren-kbd.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<kbd class="ren-kbd">⌘</kbd>+<kbd class="ren-kbd">K</kbd>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-kbd`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-kbd-bg`
- `--ren-kbd-border`
- `--ren-kbd-color`
- `--ren-kbd-font-family`
- `--ren-kbd-font-size`
- `--ren-kbd-padding`
- `--ren-kbd-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-kbd/ren-kbd.css`
- `docs/components/ren-kbd.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-link
type: contract
name: ren-link component.md
path: components/primitives/ren-link/component.md
data: {"kind":"primitive"}
body:
# ren-link Component Contract

Accessible link styling for inline, muted, nav, external, and skip links.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-link` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-link` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Link primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The element navigates to a URL, anchor, or routed page (i.e., it has a real href)."
    - "You need inline text links inside paragraphs styled with underline + hover."
    - "You need a navigation link with a 44px touch target (.ren-link-nav) plus active-route styling via aria-current=\"page\" / [data-active]."
    - "You need an external-link affordance with a trailing ↗ marker (.ren-link-external)."
    - "You need a skip-to-content link that becomes visible only on focus (.ren-link-skip)."
    - "You need a muted or unstyled (inherits color) anchor (.ren-link-muted, .ren-link-plain)."
  avoidWhen:
    - "The element triggers an imperative action (submit, open dialog, delete) — use ren-button."
    - "The element is a tab, menuitem, or pagination control — use the corresponding component."
    - "The element is a stateful toggle — use a switch, checkbox, or button with aria-pressed."

canonicalImports:
  css:
    - "rends/components/primitives/ren-link/ren-link.css"
  notes:
    - "CSS-only primitive; no JavaScript file exists for ren-link."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Always render an actual <a href=\"...\"> element. Never style a <button> or <span> as a link."
  - "External links should set rel=\"noopener noreferrer\" and target=\"_blank\" alongside .ren-link-external when opening in a new tab."
  - "Nav links representing the current page set aria-current=\"page\" (or data-active for non-routing states) on the <a>."
  - "Skip links (.ren-link-skip) must be the first focusable element in <body> and reference a real target id (e.g., href=\"#main\")."
  - "The trailing ↗ on .ren-link-external is decorative content — never use it as the only signal that a link opens externally; provide aria-label or visually-hidden text for screen readers if needed."

forbiddenPatterns:
  - "<button class=\"ren-link\"> — use a real <a> with an href."
  - "<a class=\"ren-link\" onclick=\"...\"> without an href — non-focusable, breaks keyboard activation."
  - "Removing the focus ring (outline: none) without restoring a visible :focus-visible style."
  - "Hardcoded color overrides like style=\"color:#0066cc\" — use --ren-link-color / --color-text-link."
  - "Using .ren-link-nav for inline prose links; it expects flex layout and a 44px hit area."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-link-color, --ren-link-font-weight, --ren-link-hover-color, --ren-link-underline."
    - "Semantic tokens: --color-text-link, --color-text-link-hover, --color-text, --color-text-muted, --color-accent, --color-accent-subtle, --color-on-accent, --color-focus-ring, --color-fill."
    - "Layout / motion tokens: --space-*, --radius-sm, --radius-md, --touch-min, --ring-width, --duration-tactile, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Custom underline colors via text-decoration shorthand that bypass --ren-link-underline."

accessibility:
  required:
    - "Real <a href> semantics; never simulate a link with a div + onclick."
    - "Visible :focus-visible ring driven by --color-focus-ring and --ring-width."
    - ".ren-link-nav guarantees a 44px touch target via min-height: var(--touch-min)."
    - "The current navigation item must set aria-current=\"page\" so screen readers announce route position."
    - "Skip links must be reachable as the first Tab stop and become visually focused via .ren-link-skip:focus."
    - "External-link ↗ glyph is a visual hint only; do not rely on it for assistive-tech announcement."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-link/ren-link.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<a class="ren-link" href="/docs">Read the documentation</a>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-link`
- `.ren-link-external`
- `.ren-link-muted`
- `.ren-link-nav`
- `.ren-link-plain`
- `.ren-link-skip`

## States And Attributes

- `[aria-current]`
- `[data-active]`
- `:active`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-link-color`
- `--ren-link-font-weight`
- `--ren-link-hover-color`
- `--ren-link-underline`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-link/ren-link.css`
- `docs/components/ren-link.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-pagination
type: contract
name: ren-pagination component.md
path: components/primitives/ren-pagination/component.md
data: {"kind":"primitive"}
body:
# ren-pagination Component Contract

Pagination navigation for page ranges and previous/next movement.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-pagination` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-pagination` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Pagination primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "A list, table, or grid is broken into discrete numbered pages with a known total count."
    - "You need prev / next + numbered items with aria-current=\"page\" on the active page."
    - "You need a minimal prev / info / next layout (.ren-pagination-simple) when total pages is unknown."
    - "You need a compact variant for dense tables (.ren-pagination-compact)."
    - "You need a centered standalone block under content (.ren-pagination-centered)."
    - "You need an ellipsis spacer between page ranges (.ren-pagination-ellipsis)."
  avoidWhen:
    - "The feed is infinite-scroll or load-more — use a button + scroll observer, not pagination."
    - "The data is paged by cursor with no concept of page numbers — show only prev/next."
    - "You need step navigation inside a wizard — use a stepper component."
    - "The navigation is between sibling sections of a page — use tabs or anchor links."

canonicalImports:
  css:
    - "rends/components/primitives/ren-pagination/ren-pagination.css"
  notes:
    - "CSS-only primitive; no JavaScript file exists for ren-pagination."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Wrap the control in a real <nav aria-label=\"Pagination\"> with class=\"ren-pagination\"."
  - "Each page link is an <a href=\"...\"> with class=\"ren-pagination-item\"; mark the active page with aria-current=\"page\" (it auto-disables pointer-events)."
  - "Prev / next are <a class=\"ren-pagination-prev\"> / <a class=\"ren-pagination-next\"> with aria-label describing direction when the content is a glyph (e.g., aria-label=\"Previous page\")."
  - "Use <span class=\"ren-pagination-ellipsis\" aria-hidden=\"true\">…</span> for visual gaps — it must not be focusable."
  - "Disabled prev/next use aria-disabled=\"true\" (preferred for <a>) or disabled (for <button>)."

forbiddenPatterns:
  - "<div role=\"navigation\"> or <ul> instead of <nav> for the wrapper."
  - "Plain <span> or <button> styled as a pagination item — use <a class=\"ren-pagination-item\">."
  - "Marking the active page with only a CSS class — must include aria-current=\"page\" for screen readers."
  - "Hiding disabled prev/next via display: none — keep them in DOM with aria-disabled so layout stays stable."
  - "Hardcoded color overrides on the active item that bypass --color-accent / --color-on-accent."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-pagination-active-bg, --ren-pagination-active-color, --ren-pagination-bg, --ren-pagination-font-size, --ren-pagination-gap, --ren-pagination-hover-bg, --ren-pagination-radius, --ren-pagination-size."
    - "Semantic tokens consumed internally: --color-text, --color-text-muted, --color-accent, --color-on-accent, --color-fill, --color-fill-hover, --color-disabled-text, --color-focus-ring."
    - "Layout tokens: --touch-min, --size-sm, --space-1, --space-2, --radius-md, --stroke-1, --ring-width, --ring-offset-width, --duration-tactile, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Inline width/height styling that breaks min 44px touch target on default variant."

accessibility:
  required:
    - "Wrapper is a real <nav> landmark with aria-label=\"Pagination\" (or equivalent)."
    - "Active page uses aria-current=\"page\"; do not duplicate it on a separate <span>."
    - "Default and simple variants guarantee min 44px touch targets (--touch-min); compact variant is for non-touch contexts only."
    - "Visible :focus-visible ring driven by --color-focus-ring + --ring-width / --ring-offset-width."
    - "Glyph-only prev/next provide an aria-label so the direction is announced."
    - "Disabled prev/next set aria-disabled=\"true\" AND pointer-events: none (handled by CSS); do not rely on visual dimming alone."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-pagination/ren-pagination.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<nav class="ren-pagination" aria-label="Pagination">
  <a class="ren-pagination-prev" href="?page=1" aria-label="Previous page">‹</a>
  <a class="ren-pagination-item" href="?page=1">1</a>
  <a class="ren-pagination-item" href="?page=2" aria-current="page">2</a>
  <a class="ren-pagination-next" href="?page=3" aria-label="Next page">›</a>
</nav>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-pagination`
- `.ren-pagination-centered`
- `.ren-pagination-compact`
- `.ren-pagination-ellipsis`
- `.ren-pagination-info`
- `.ren-pagination-item`
- `.ren-pagination-next`
- `.ren-pagination-prev`
- `.ren-pagination-simple`

## States And Attributes

- `[aria-current]`
- `[aria-disabled]`
- `:active`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-pagination-active-bg`
- `--ren-pagination-active-color`
- `--ren-pagination-bg`
- `--ren-pagination-font-size`
- `--ren-pagination-gap`
- `--ren-pagination-hover-bg`
- `--ren-pagination-radius`
- `--ren-pagination-size`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-pagination/ren-pagination.css`
- `docs/components/ren-pagination.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-progress
type: contract
name: ren-progress component.md
path: components/primitives/ren-progress/component.md
data: {"kind":"primitive"}
body:
# ren-progress Component Contract

Progress and meter styling for determinate and indeterminate completion states.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-progress` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-progress` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Progress primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a determinate horizontal bar showing percent completion of a known task (.ren-progress + .ren-progress-bar with inline width)."
    - "You need an indeterminate loading bar with sliding animation (.ren-progress-indeterminate)."
    - "You need a semantic-colored progress state (success / warning / danger / info)."
    - "You need a meter (current value within a min/max range, not progress) — use <meter class=\"ren-meter\">."
    - "You need optional caption rows above (.ren-progress-label) and below (.ren-progress-value) the bar."
    - "You need multiple bar heights: sm (4px), md (8px default), lg (12px), xl (16px)."
  avoidWhen:
    - "You need a circular spinner — use .ren-icon-spin on a loading glyph."
    - "You need a busy state on a button — use ren-button with data-loading / aria-busy."
    - "You need a step indicator with discrete milestones — use a stepper component."
    - "You need a skeleton placeholder while content loads — use ren-skeleton."

canonicalImports:
  css:
    - "rends/components/primitives/ren-progress/ren-progress.css"
  notes:
    - "CSS-only primitive; no JavaScript file exists for ren-progress."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Determinate progress: <div class=\"ren-progress\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"<pct>\"><div class=\"ren-progress-bar\" style=\"width: <pct>%\"></div></div>."
  - "Indeterminate progress: add class .ren-progress-indeterminate to the outer and omit aria-valuenow (set aria-valuetext or aria-busy=\"true\" instead)."
  - "Native semantics alternative: <progress class=\"ren-progress\" max=\"100\" value=\"<pct>\"></progress> when no custom inner bar is needed."
  - "Meter: <meter class=\"ren-meter\" min=\"0\" max=\"10\" value=\"<n>\"> — for a current value in a range (battery, score), not for progress over time."
  - "Provide an accessible label via aria-label, aria-labelledby, or a sibling .ren-progress-label."

forbiddenPatterns:
  - "<div class=\"ren-progress\"> without role=\"progressbar\" + aria-value* (when not using native <progress>)."
  - "Animating bar width with a custom transition that bypasses --duration-state / --ease-enter."
  - "Using .ren-progress-danger to convey state by color alone — pair with a text label."
  - "Hardcoded background colors on .ren-progress-bar that bypass --color-accent / semantic variants."
  - "Using <meter> for a progress-over-time scenario or <progress> for a current-value-in-range scenario."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-progress-bg, --ren-progress-duration, --ren-progress-easing, --ren-progress-fill, --ren-progress-height, --ren-progress-radius."
    - "Semantic tokens consumed internally: --color-fill, --color-accent, --color-success, --color-warning, --color-danger, --color-info, --color-text, --color-text-secondary."
    - "Layout / type tokens: --radius-full, --label-size, --weight-medium, --weight-regular, --space-1, --duration-state, --ease-enter."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Raw animation durations; rely on --ren-progress-duration / --duration-state."

accessibility:
  required:
    - "Determinate bars expose role=\"progressbar\" + aria-valuemin / aria-valuemax / aria-valuenow (or use native <progress>)."
    - "Indeterminate bars omit aria-valuenow and set aria-busy=\"true\" or aria-valuetext=\"Loading\" so AT can announce status."
    - "Every progress bar has an accessible name (aria-label, aria-labelledby, or a .ren-progress-label paired by id)."
    - "Color-coded variants (success / warning / danger / info) must be paired with a text label or .ren-progress-value — never color-only."
    - "Indeterminate animation auto-honors prefers-reduced-motion: reduce (slide is suppressed, bar holds at 50%)."
    - "Width transition is suppressed under reduced motion so value updates do not animate."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-progress/ren-progress.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div class="ren-progress" role="progressbar" aria-label="Upload progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="65">
  <div class="ren-progress-bar" style="width: 65%"></div>
</div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-meter`
- `.ren-meter-lg`
- `.ren-meter-sm`
- `.ren-meter-xl`
- `.ren-progress`
- `.ren-progress-bar`
- `.ren-progress-danger`
- `.ren-progress-indeterminate`
- `.ren-progress-info`
- `.ren-progress-label`
- `.ren-progress-lg`
- `.ren-progress-sm`
- `.ren-progress-success`
- `.ren-progress-value`
- `.ren-progress-warning`
- `.ren-progress-xl`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-progress-bg`
- `--ren-progress-duration`
- `--ren-progress-easing`
- `--ren-progress-fill`
- `--ren-progress-height`
- `--ren-progress-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-progress/ren-progress.css`
- `docs/components/ren-progress.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-radio
type: contract
name: ren-radio component.md
path: components/primitives/ren-radio/component.md
data: {"kind":"primitive"}
body:
# ren-radio Component Contract

Native radio group styling with optional custom element keyboard enhancement.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-radio` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-radio` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Radio primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The user must pick exactly one option from 2-7 mutually-exclusive choices."
    - "All choices should be visible at once (no overflow / collapse)."
    - "You need a custom-styled radio dot built on native <input type=\"radio\"> with preserved semantics."
    - "You need keyboard arrow navigation + roving tabindex via <ren-radio-group>."
    - "You need a vertical (.ren-radio-group) or horizontal (.ren-radio-group-horizontal) layout."
  avoidWhen:
    - "There are more than ~7 options or the list overflows — use a ren-select / combobox."
    - "Multiple selections are allowed — use ren-checkbox."
    - "The choice is binary on/off — use ren-switch or ren-checkbox."
    - "The selection drives navigation or filters with immediate side effects — consider ren-segmented or tabs."

canonicalImports:
  css:
    - "rends/components/primitives/ren-radio/ren-radio.css"
  js:
    - "rends/components/primitives/ren-radio/ren-radio.js"
  notes:
    - "JS is only required when using <ren-radio-group> for arrow-key roving tabindex; a plain <fieldset role=\"radiogroup\"> with .ren-radio labels works CSS-only with native browser radio behavior."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Each option is a <label class=\"ren-radio\"> containing a real <input type=\"radio\" name=\"<group>\">, a <span class=\"ren-radio-control\"></span>, and the visible label text — in that DOM order so the :checked + .ren-radio-control adjacent selector works."
  - "All radios in one group share the same name attribute so the browser enforces single-selection."
  - "Wrap the group in <ren-radio-group> (auto-sets role=\"radiogroup\") or in a <fieldset> with <legend> + role=\"radiogroup\" when no JS is desired."
  - "Use <ren-radio-group orientation=\"horizontal\"> to switch to row layout; the JS swaps the class to .ren-radio-group-horizontal."
  - "Never set tabindex manually on inputs inside <ren-radio-group> — the roving-tabindex utility manages it."

forbiddenPatterns:
  - "Replacing <input type=\"radio\"> with a styled <div role=\"radio\"> — loses form submission + native a11y."
  - "Hiding the input with display: none (breaks focus) — use the built-in visually-hidden clip-path pattern."
  - "Two radios with different name attributes in the same group (allows multi-selection)."
  - "Using .ren-radio for an icon-only toggle without a visible text label or aria-label."
  - "Animating the dot via custom keyframes that bypass --duration-state / --ease-playful."

tokenPolicy:
  allowed:
    - "Semantic tokens consumed internally: --color-text, --color-border-strong, --color-accent, --color-accent-hover, --color-on-accent, --color-focus-ring."
    - "Layout / motion tokens: --space-2, --space-3, --space-4, --radius-full, --body-size, --weight-regular, --touch-min, --ring-width, --ring-offset-width, --duration-state, --duration-tactile, --ease-enter, --ease-playful."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors in consumer overrides."
    - "Custom radio sizes that fall below the 20px control + 44px label touch target."

accessibility:
  required:
    - "Real <input type=\"radio\"> elements so the browser handles single-selection, form submission, and assistive-tech announcement."
    - ".ren-radio label has min-height: var(--touch-min) so the whole row is a 44px touch target."
    - "Visible :focus-visible ring on .ren-radio-control driven by --color-focus-ring (the native input is visually hidden but focus is delegated to its sibling)."
    - "<ren-radio-group> sets role=\"radiogroup\" and supports arrow-key navigation (Up/Down vertical, Left/Right horizontal) with loop; selection follows focus."
    - "Disabled options use <input disabled> (handled via :has(input:disabled)) — do not rely on opacity alone."
    - "Provide a group label via <legend> inside <fieldset> or aria-label / aria-labelledby on <ren-radio-group>."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-radio/ren-radio.css">
<script type="module" src="rends/components/primitives/ren-radio/ren-radio.js"></script>
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<ren-radio-group>
  <label class="ren-radio"><input type="radio" name="plan" value="basic"><span class="ren-radio-control"></span><span>Basic</span></label>
  <label class="ren-radio"><input type="radio" name="plan" value="pro"><span class="ren-radio-control"></span><span>Pro</span></label>
</ren-radio-group>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-radio`
- `.ren-radio-control`
- `.ren-radio-group`
- `.ren-radio-group-horizontal`

## States And Attributes

- `:active`
- `:checked`
- `:disabled`
- `:focus-visible`
- `:hover`

## Public Token API

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

If no `--ren-*` token is detected here, theme through semantic tokens from `tokens/tokens.md` and avoid selector overrides.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- Keep JS behavior progressive: the visual structure should remain understandable before enhancement.

## Related Files

- `components/primitives/ren-radio/ren-radio.css`
- `components/primitives/ren-radio/ren-radio.js`
- `docs/components/ren-radio.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-separator
type: contract
name: ren-separator component.md
path: components/primitives/ren-separator/component.md
data: {"kind":"primitive"}
body:
# ren-separator Component Contract

Visual or semantic divider between content groups.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-separator` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-separator` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Separator primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a horizontal divider between content groups inside a flow (use <hr class=\"ren-separator\">)."
    - "You need a vertical divider inside a flex/inline row (use .ren-separator-vertical)."
    - "You need a labeled divider (e.g. \"or\" between auth options) using .ren-separator-label."
    - "The divider is purely visual and does not need any interactivity or focus."
    - "You want spacing tokens (--space-3 / --space-4) baked into the divider's margin instead of ad-hoc CSS."
  avoidWhen:
    - "You need a sectioning landmark — use semantic <section> / <aside> with headings instead."
    - "The divider should also collapse the sidebar / scroll region — use ren-sidebar / a layout primitive."
    - "You want a decorative border around a card — use the card's --color-border, not a separator."

canonicalImports:
  css:
    - "rends/components/primitives/ren-separator/ren-separator.css"
  notes:
    - "CSS-only primitive. There is no colocated JS — do not import one."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Horizontal separators must be a real <hr class=\"ren-separator\"> element so AT exposes the implicit separator role."
  - "Vertical separators use a non-<hr> element (<div class=\"ren-separator-vertical\">) inside a flex row so align-self: stretch can take effect."
  - "Labeled separators use a flex container: <div class=\"ren-separator-label\">or</div>; the ::before / ::after pseudo-elements draw the lines automatically — do not add extra <span> rules."
  - "Do not nest interactive content inside a separator; it is decorative chrome only."

forbiddenPatterns:
  - "<div class=\"ren-separator\"> for horizontal dividers — use the native <hr> so screen readers announce the separator."
  - "border-top: 1px solid #... or border-bottom: 1px solid #... in consumer code; use .ren-separator and let --color-separator drive the color."
  - "Inline margin overrides (style=\"margin: 0\") to tighten spacing — set --space-* tokens at a parent scope instead."
  - "Using .ren-separator-vertical outside a flex / grid container that gives it a measurable cross-axis height."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-separator-color, --ren-separator-margin, --ren-separator-width."
    - "Semantic tokens consumed internally: --color-separator, --color-text-muted, --space-3, --space-4, --caption-size."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgb / named colors for the separator line."
    - "Raw pixel widths in inline styles; use --ren-separator-width."

accessibility:
  required:
    - "Horizontal separators are <hr> so the implicit role=\"separator\" is exposed to assistive technology."
    - "Purely decorative vertical separators may use aria-hidden=\"true\" if they add no information beyond a sibling label."
    - "Labels on .ren-separator-label must remain real text (not background-image) so they are announced and translatable."
    - "Color contrast is not required for decorative separators, but the line must remain visible in both light and dark themes via --color-separator."
    - "Do not add focusable behavior; separators are not interactive."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-separator/ren-separator.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<hr class="ren-separator">

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-separator`
- `.ren-separator-horizontal`
- `.ren-separator-label`
- `.ren-separator-vertical`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-separator-color`
- `--ren-separator-margin`
- `--ren-separator-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-separator/ren-separator.css`
- `docs/components/ren-separator.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-skeleton
type: contract
name: ren-skeleton component.md
path: components/primitives/ren-skeleton/component.md
data: {"kind":"primitive"}
body:
# ren-skeleton Component Contract

Loading placeholder primitive that respects reduced motion.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-skeleton` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-skeleton` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Skeleton primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "Content is asynchronously loading and you need a placeholder that preserves layout dimensions."
    - "You want a shimmer that automatically degrades to a static fill under prefers-reduced-motion."
    - "You need shape-specific placeholders: text line (.ren-skeleton-text), heading (.ren-skeleton-heading), avatar (.ren-skeleton-circle), or block (.ren-skeleton-rect)."
    - "The placeholder is short-lived — it will be replaced by real content within a few seconds."
  avoidWhen:
    - "The wait is indeterminate or very long — use ren-spinner with descriptive copy."
    - "Only a single small inline indicator is needed — use ren-spinner-xs or ren-spinner-sm."
    - "You want a progress bar with a known percentage — use ren-progress instead."

canonicalImports:
  css:
    - "rends/components/primitives/ren-skeleton/ren-skeleton.css"
  notes:
    - "CSS-only primitive. There is no colocated JS — do not import one."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Always combine the base .ren-skeleton with one shape modifier (.ren-skeleton-text, .ren-skeleton-heading, .ren-skeleton-circle, .ren-skeleton-rect) so the size token resolves."
  - "Wrap a group of skeletons in a container that sets aria-busy=\"true\" and aria-live=\"polite\" so assistive tech is told to wait."
  - "Replace skeleton nodes with real content as soon as the data resolves — do not leave them mounted indefinitely."
  - "Match the skeleton's dimensions to the final content (avatar size, line count) to avoid layout shift on swap."

forbiddenPatterns:
  - "Animating with background-image: linear-gradient(...) hex colors directly; use the .ren-skeleton class and let --color-fill / --color-fill-hover drive the shimmer."
  - "Hardcoded width: 60% or height: 1.5em overrides for headings; use .ren-skeleton-heading and override --avatar-md / --radius-* via tokens if needed."
  - "Leaving the skeleton in the DOM after content loads — toggle visibility / remove, never repurpose as decoration."
  - "Using a skeleton for a determinate progress state (e.g. uploading X of Y) — that is a job for ren-progress."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-skeleton-bg, --ren-skeleton-radius, --ren-skeleton-shine, --ren-skeleton-speed."
    - "Semantic tokens consumed internally: --color-fill, --color-fill-hover, --radius-sm, --radius-md, --radius-full, --avatar-md, --duration-loop-slow, --ease-loop-pulse."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgba shimmer colors."
    - "Custom animation durations in seconds; theme via --ren-skeleton-speed or --duration-loop-slow."

accessibility:
  required:
    - "The container holding skeleton placeholders must set aria-busy=\"true\" so AT announces the loading state."
    - "Pair aria-busy with aria-live=\"polite\" when the content swap should be announced once data resolves."
    - "Skeleton nodes are presentational; do not put role=\"img\" or descriptive alt-text on them."
    - "The shimmer animation must stop under prefers-reduced-motion (the CSS already handles this — do not override animation in consumer code)."
    - "Skeleton color must remain perceivable in both light and dark themes via --color-fill (do not pin to a specific gray)."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-skeleton/ren-skeleton.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<div aria-busy="true" aria-live="polite"><div class="ren-skeleton ren-skeleton-heading"></div><div class="ren-skeleton ren-skeleton-text"></div></div>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-skeleton`
- `.ren-skeleton-circle`
- `.ren-skeleton-heading`
- `.ren-skeleton-rect`
- `.ren-skeleton-text`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-skeleton-bg`
- `--ren-skeleton-radius`
- `--ren-skeleton-shine`
- `--ren-skeleton-speed`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-skeleton/ren-skeleton.css`
- `docs/components/ren-skeleton.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-spinner
type: contract
name: ren-spinner component.md
path: components/primitives/ren-spinner/component.md
data: {"kind":"primitive"}
body:
# ren-spinner Component Contract

Small loading indicator for pending or busy states.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-spinner` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-spinner` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Spinner primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "You need a small inline busy / pending indicator with no known duration."
    - "You need to indicate loading inside a button or near a single control (xs / sm sizes)."
    - "The spinner sits on a dark / colored surface — use .ren-spinner-light."
    - "The wait is short and content layout is not yet known (otherwise prefer ren-skeleton for layout preservation)."
    - "You want CSS-only animation that automatically degrades to an opacity pulse under prefers-reduced-motion."
  avoidWhen:
    - "Content placeholders that preserve layout — use ren-skeleton for cards, lists, avatars."
    - "Determinate progress (you know the percentage) — use ren-progress."
    - "Long-running background operations where the user can navigate away — use ren-toast / ren-banner status messaging instead of a persistent spinner."

canonicalImports:
  css:
    - "rends/components/primitives/ren-spinner/ren-spinner.css"
  notes:
    - "CSS-only primitive. There is no colocated JS — do not import one."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Render a <span class=\"ren-spinner\"> (or another inline-block element) with role=\"status\" and an aria-label like \"Loading\"."
  - "Add a size modifier when the default 1.5rem is wrong: .ren-spinner-xs (0.875rem), .ren-spinner-sm (1rem), .ren-spinner-lg (2rem), .ren-spinner-xl (3rem)."
  - "Use .ren-spinner-light only on dark / colored surfaces where the default --color-accent ring would lack contrast."
  - "When a spinner replaces a button's label, also set aria-busy=\"true\" on the button so AT announces the busy state."

forbiddenPatterns:
  - "<div class=\"ren-spinner\" aria-hidden=\"true\"> with no accessible label when the spinner is the only signal of busy state."
  - "Hardcoded border-color: #... overrides; theme via --ren-spinner-color or use .ren-spinner-light for inverted surfaces."
  - "Custom animation-duration values in inline styles; use --ren-spinner-speed (or --duration-loop)."
  - "Combining .ren-spinner-light with --color-accent overrides — pick one approach; light is for inverted surfaces only."
  - "Spinner left mounted after the operation finishes — remove it or replace with success / error state."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-spinner-color, --ren-spinner-size, --ren-spinner-speed, --ren-spinner-width."
    - "Semantic tokens consumed internally: --color-fill-active, --color-accent, --white, --radius-full, --duration-loop, --ease-loop-smooth, --ease-loop-pulse."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgba border colors."
    - "Raw animation: spin 1s linear infinite; use the .ren-spinner class and --ren-spinner-speed."

accessibility:
  required:
    - "The spinner must have role=\"status\" and an aria-label (e.g. \"Loading\") when it conveys busy state."
    - "When the spinner replaces a button label, set aria-busy=\"true\" on the button so the busy state is exposed once."
    - "Do not stack multiple labeled spinners in the same live region — they will be announced repeatedly."
    - "Reduced-motion users see a gentle opacity pulse (built-in); do not override animation in consumer code."
    - "Color is not the only signal: the spinner is also a moving shape. Ensure the rotation / pulse is visible against the background."
    - "Provide a textual fallback (\"Saving…\") near the spinner whenever the wait can exceed a couple of seconds."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-spinner/ren-spinner.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<span class="ren-spinner" role="status" aria-label="Loading"></span>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-spinner`
- `.ren-spinner-lg`
- `.ren-spinner-light`
- `.ren-spinner-sm`
- `.ren-spinner-xl`
- `.ren-spinner-xs`

## States And Attributes

- `None detected in the colocated CSS/JS. Check related files before inventing one.`

## Public Token API

- `--ren-spinner-color`
- `--ren-spinner-size`
- `--ren-spinner-speed`
- `--ren-spinner-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-spinner/ren-spinner.css`
- `docs/components/ren-spinner.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-switch
type: contract
name: ren-switch component.md
path: components/primitives/ren-switch/component.md
data: {"kind":"primitive"}
body:
# ren-switch Component Contract

Toggle switch primitive built on native `<input type="checkbox" role="switch">` with checked, hover, focus, active, and disabled states.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-switch` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-switch` primitive.
- Keep generated markup aligned with the colocated CSS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- The UI requires an instant on/off state that takes effect immediately (e.g. enable/disable a setting, toggle dark mode).
- The control sits in a settings panel or in-line preference area, not in a form that must be submitted before changes apply.
- Native checkbox semantics with `role="switch"` are appropriate.

## Do Not Use When

- The choice should only take effect on form submit — use `ren-checkbox` instead.
- More than two states are possible — use `ren-radio` or `ren-toggle-group`.
- The control must look like a checkbox (square box with checkmark) — use `ren-checkbox`.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The control flips an immediate on/off setting (dark mode, notifications, feature flags)."
    - "The change should take effect the moment the user toggles it, with no submit step."
    - "Native checkbox semantics extended with role=\"switch\" are the right a11y model."
    - "You want the iOS-style 51×31 track plus full label wrapper for a 44px touch target."
    - "You need the checked color to come from --color-success (positive enablement), not the brand accent."
  avoidWhen:
    - "The change must wait for a Save / Submit step — use ren-checkbox so users can review before commit."
    - "There are 3+ choices or the option is mutually exclusive within a set — use ren-radio or ren-toggle-group."
    - "The control needs a checkmark glyph to communicate selection — use ren-checkbox."
    - "The control is a tab / segmented selector across views — use ren-tabs or ren-toggle-group."

canonicalImports:
  css:
    - "rends/components/primitives/ren-switch/ren-switch.css"
  notes:
    - "CSS-only primitive. There is no colocated JS — do not import one."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Wrap the control in a <label class=\"ren-switch\"> so the entire label area is the click / touch target."
  - "Use a real <input type=\"checkbox\" role=\"switch\"> as the first child; the CSS hides it visually but keeps it focusable and announced."
  - "Follow the input with <span class=\"ren-switch-track\"></span>; the thumb is the track's ::after pseudo-element — do not add a separate thumb element."
  - "Include a visible <span>Label</span> sibling for sighted users; if the switch is icon-only, set aria-label on the input."
  - "Persist on/off changes immediately via the input's change event — never defer to a separate Save action."

forbiddenPatterns:
  - "<div role=\"switch\" tabindex=\"0\"> custom toggles; always use a real <input type=\"checkbox\" role=\"switch\">."
  - "Hiding the input with display: none — it removes it from the accessibility tree. Keep the existing clip-path / visually-hidden pattern."
  - "Hardcoded track / thumb colors via background-color in inline styles; theme via --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-thumb-color."
  - "Putting the switch inside a <form> where it only applies after submit — that is checkbox territory."
  - "Removing the focus outline on .ren-switch-track without restoring an equivalent :focus-visible ring."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-switch-bg, --ren-switch-checked-bg, --ren-switch-duration, --ren-switch-easing, --ren-switch-height, --ren-switch-thumb-color, --ren-switch-thumb-size, --ren-switch-width."
    - "Semantic tokens consumed internally: --color-fill-active, --color-fill-hover, --color-success, --color-success-strong, --color-text, --color-focus-ring, --white, --radius-full, --ring-width, --ring-offset-width, --touch-min, --shadow-sm."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / named colors for the checked / unchecked track."
    - "Custom transition values; use --ren-switch-duration and --ren-switch-easing."

accessibility:
  required:
    - "The native <input type=\"checkbox\" role=\"switch\"> must remain in the DOM and focusable — assistive tech announces it as a switch with on/off states."
    - "The whole <label class=\"ren-switch\"> wrapper provides a touch target of at least --touch-min (44px)."
    - "Focus must show the outline ring on .ren-switch-track via :focus-visible (the CSS already wires this up; do not remove it)."
    - "Do not signal state by color alone — the thumb position (left vs right) is the primary visual cue."
    - "When the switch is disabled, set disabled on the <input> (the parent .ren-switch:has(input:disabled) handles styling) instead of a custom data attribute."
    - "If the switch's label is not adjacent text, set aria-label or aria-labelledby on the <input>."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-switch/ren-switch.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<label class="ren-switch">
  <input type="checkbox" role="switch">
  <span class="ren-switch-track"></span>
  <span>Dark mode</span>
</label>
```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-switch`
- `.ren-switch-track`

## States And Attributes

- `:active`
- `:checked`
- `:disabled`
- `:focus-visible`
- `:hover`
- `role="switch"` on the native input (announces toggle semantics to assistive technology)

## Public Token API

- `--ren-switch-bg`
- `--ren-switch-checked-bg`
- `--ren-switch-duration`
- `--ren-switch-easing`
- `--ren-switch-height`
- `--ren-switch-thumb-color`
- `--ren-switch-thumb-size`
- `--ren-switch-width`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; the `role="switch"` on the input announces the on/off toggle pattern.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum (the track is 51×31 but the label wrapper provides the full touch target).
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone — the thumb position is the primary visual signal.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-switch/ren-switch.css`
- `docs/components/ren-switch.html`
- `components/primitives/ren-checkbox/component.md` (sibling primitive — submit-time vs immediate semantics)
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: contract:primitive:ren-tag
type: contract
name: ren-tag component.md
path: components/primitives/ren-tag/component.md
data: {"kind":"primitive"}
body:
# ren-tag Component Contract

Compact keyword, filter, or removable token primitive.

Load this file after `ren-design.md` and before generating, editing, or reviewing `ren-tag` UI.

## Purpose

- Provide the agent-facing public contract for the `ren-tag` primitive.
- Keep generated markup aligned with the colocated CSS/JS source.
- Route theming through semantic tokens and the component token API instead of ad hoc styles.

## Use When

- You need the Tag primitive behavior or visual role.
- The UI should stay inside RenDS' vanilla HTML/CSS/JS conventions.
- The implementation can use the public selectors, states, and imports listed here.

## Do Not Use When

- Native HTML plus `base/primitive-zero.md` is enough.
- A simpler primitive can express the UI without this primitive.
- You would need to invent undocumented selectors, states, or JavaScript APIs.

## aiHints

```yaml
selectionCriteria:
  useWhen:
    - "The user picks, adds, or removes a value (filter chip, selected keyword, attached label)."
    - "You need a removable token with an embedded dismiss button (.ren-tag-dismiss)."
    - "You need a toggleable filter pill driven by aria-pressed or data-selected (.ren-tag-clickable)."
    - "You need a status-tinted token (.ren-tag-primary / .ren-tag-success / .ren-tag-warning / .ren-tag-danger) tied to a semantic color."
    - "Multiple tokens flow together in a wrap — use .ren-tag-group as the parent."
  avoidWhen:
    - "The element is a static, non-interactive status pill — use ren-badge."
    - "The element triggers an imperative action (submit, open dialog) — use ren-button."
    - "The element is a hyperlink to another page — use ren-link."
    - "The element is a form-style multi-select dropdown option — use ren-select / ren-combobox option markup."

canonicalImports:
  css:
    - "rends/components/primitives/ren-tag/ren-tag.css"
  notes:
    - "CSS-only primitive. There is no colocated JS — wire up dismiss/click handlers in your own script."
    - "If the page already imports rends/components/index.css, do not import the CSS again."

requiredMarkup:
  - "Render the tag as an inline element: <span class=\"ren-tag\"> for static, <span class=\"ren-tag ren-tag-clickable\" tabindex=\"0\" role=\"button\"> for toggleable filters."
  - "Removable tags must include <button class=\"ren-tag-dismiss\" type=\"button\" aria-label=\"Remove <name>\">; never use a non-button element for dismissal."
  - "Toggle / filter tags must mirror selection state via aria-pressed=\"true|false\" (preferred) or data-selected; do not rely on color alone."
  - "Group multiple tags inside a parent with class=\"ren-tag-group\" so the gap and wrap behavior come from the design system."
  - "Combine at most one color variant (.ren-tag-primary | .ren-tag-success | .ren-tag-warning | .ren-tag-danger) with at most one size (.ren-tag-sm | .ren-tag-lg)."

forbiddenPatterns:
  - "<div class=\"ren-tag\" onclick=...> for clickable tags — use a real <button> or .ren-tag-clickable with role=\"button\" and tabindex=\"0\"."
  - "Custom dismiss icons drawn with background-image; place a text \"×\" or an <svg aria-hidden=\"true\"> inside .ren-tag-dismiss instead."
  - "Hardcoded background-color: #... overrides; theme via --ren-tag-bg / --ren-tag-border-color / --ren-tag-color."
  - "Using ren-tag-danger purely for emphasis on a non-danger value — semantic color variants must match meaning (danger = destructive / error)."
  - "Removing the :focus-visible outline on .ren-tag-clickable or .ren-tag-dismiss without restoring a visible ring."

tokenPolicy:
  allowed:
    - "Component tokens: --ren-tag-bg, --ren-tag-border-color, --ren-tag-color, --ren-tag-font-size, --ren-tag-font-weight, --ren-tag-gap, --ren-tag-height, --ren-tag-padding-x, --ren-tag-radius."
    - "Semantic tokens consumed internally: --color-text, --color-text-muted, --color-fill, --color-fill-hover, --color-fill-active, --color-border, --color-border-strong, --color-accent, --color-accent-subtle, --color-on-accent, --color-success / --color-success-subtle / --color-success-strong, --color-warning / --color-warning-subtle / --color-warning-strong, --color-danger / --color-danger-subtle / --color-danger-strong, --color-focus-ring, --stroke-1, --radius-full, --ring-width, --ring-offset-width."
  forbidden:
    - "Primitive palette tokens (--blue-*, --gray-*, --red-*, --green-*, --orange-*, --yellow-*, --teal-*, --purple-*, --pink-*) in consumer code."
    - "Hardcoded hex / rgba background and border colors."
    - "Inline padding / font-size overrides; pick the .ren-tag-sm / .ren-tag-lg variant or override --ren-tag-padding-x / --ren-tag-font-size."

accessibility:
  required:
    - "Clickable tags must be focusable: either a real <button>, or use .ren-tag-clickable with tabindex=\"0\" and role=\"button\" plus Space / Enter handling."
    - "Toggle state must be exposed via aria-pressed; data-selected is for styling parity only, not a substitute for ARIA."
    - "Dismiss buttons need an aria-label that names the value being removed (e.g. \"Remove React\"), not just \"Remove\"."
    - "Color variants must not be the sole signal of meaning — pair .ren-tag-danger with text or an icon that conveys the same state."
    - ":focus-visible outline must remain visible on both .ren-tag-clickable and .ren-tag-dismiss; the CSS wires this to --color-focus-ring."
    - "Long tag text truncates with ellipsis (.ren-tag > span overflow rule); when this happens, surface the full value via title or an off-screen label."
```

## Required Imports

```html
<link rel="stylesheet" href="rends/components/primitives/ren-tag/ren-tag.css">
<!-- No colocated JavaScript file detected. -->
```

If the page already imports `rends/components/index.css`, do not import the CSS twice.

## Canonical Markup

```html
<span class="ren-tag ren-tag-removable">Accessibility<button class="ren-tag-dismiss" type="button" aria-label="Remove Accessibility">×</button></span>

```

Use the docs page and source files listed below for full examples before adding production markup.

## Variants And Public Selectors

- `.ren-tag`
- `.ren-tag-clickable`
- `.ren-tag-danger`
- `.ren-tag-dismiss`
- `.ren-tag-group`
- `.ren-tag-lg`
- `.ren-tag-primary`
- `.ren-tag-sm`
- `.ren-tag-success`
- `.ren-tag-warning`

## States And Attributes

- `[aria-pressed]`
- `[data-selected]`
- `:active`
- `:focus-visible`
- `:hover`

## Public Token API

- `--ren-tag-bg`
- `--ren-tag-border-color`
- `--ren-tag-color`
- `--ren-tag-font-size`
- `--ren-tag-font-weight`
- `--ren-tag-gap`
- `--ren-tag-height`
- `--ren-tag-padding-x`
- `--ren-tag-radius`

Theme through these public custom properties before reaching for selectors.

## Accessibility Contract

- Preserve native semantics first; add ARIA only when semantic HTML is insufficient.
- Keep visible keyboard focus via RenDS focus tokens and `:focus-visible`.
- Keep interactive hit areas at 44px minimum unless this file's source clearly defines a smaller non-touch target.
- Respect reduced motion by using RenDS duration/easing tokens only.
- Do not communicate state through color alone.
- This contract is CSS-first; do not introduce JavaScript unless the source component already owns that behavior.

## Related Files

- `components/primitives/ren-tag/ren-tag.css`
- `docs/components/ren-tag.html`
- `ren-design.md`
- `tokens/tokens.md`
- `base/layouts.md`

## Test Expectations

- Run component or docs a11y coverage when markup, states, or ARIA change.
- Run CSS lint when selectors, tokens, or visual states change.
- Manually verify light/dark themes when color, surface, border, or shadow behavior changes.


id: docs:docs/components/ren-accordion.html
type: docs_page
name: ren-accordion docs
path: docs/components/ren-accordion.html
data: {}
body:
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Accordion — RenDS Components</title>
  <link rel="stylesheet" href="../../index.css">
  <link rel="stylesheet" href="../../components/index.css">
  <link rel="stylesheet" href="../../themes/appearance.css">
  <link rel="stylesheet" href="../../tokens/component/tokens.css">
  <link rel="stylesheet" href="../../site/shell.css">
  <style>
    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }
    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }
    .dx-demo-code { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }
    .demo-acc { background: var(--color-surface); border-radius: var(--radius-md); overflow: hidden; }
    .demo-acc details { border-bottom: 1px solid var(--color-border); }
    .demo-acc details:last-child { border-bottom: 0; }
    .demo-acc summary { list-style: none; padding: var(--space-3) var(--space-4); cursor: pointer; font-weight: var(--weight-medium); display: flex; justify-content: space-between; align-items: center; }
    .demo-acc summary::-webkit-details-marker { display: none; }
    .demo-acc summary::marker { display: none; content: ''; }
    .demo-acc summary::after {
      content: '';
      width: 16px; height: 16px;
      background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
      background-repeat: no-repeat;
      background-position: center;
      background-size: contain;
      border: none;             /* override classless.css base chevron drawn with borders */
      transform: rotate(0deg);  /* override classless.css base rotate(-45deg) */
      transition: transform 150ms;
      flex-shrink: 0;
    }
    .demo-acc details[open] summary::after { transform: rotate(180deg); }
    .demo-acc summary:hover { background: var(--color-fill); }
    .demo-acc-body { padding: 0 var(--space-4) var(--space-4); color: var(--color-text-secondary); font-size: var(--text-sm); line-height: 1.6; }
  </style>
</head>
<body>
  <header class="dx-nav"><div class="dx-nav-inner"><a href="../index.html" class="dx-brand"><span class="dx-brand-mark">R</span><span>RenDS</span><span class="ren-badge ren-badge-secondary" style="margin-left: var(--space-1);">v0.12.0</span></a><nav class="dx-nav-menu" aria-label="Primary"><a href="../index.html">Docs</a><a href="../components.html" aria-current="page">Components</a><a href="../../templates/index.html">Templates</a><a href="../../create/index.html">Theme Builder</a></nav><div class="dx-nav-actions"><a href="https://github.com/Rensoconese/ren10" class="ren-btn ren-btn-ghost ren-btn-sm">GitHub</a><a href="../getting-started.html" class="ren-btn ren-btn-primary ren-btn-sm">Get started</a></div></div></header>

  <div class="dx-shell dx-shell-grid">
    <aside class="dx-sidebar" aria-label="Site navigation">
      <h3>Guides</h3>
      <ul>
        <li><a href="../getting-started.html">Getting Started</a></li>
        <li><a href="../theming.html">Theming</a></li>
        <li><a href="../accessibility.html">Accessibility</a></li>
        <li><a href="../cli.html">CLI</a></li>
      </ul>

      <h3>Foundations</h3>
      <ul>
        <li><a href="../primitive-zero.html">Primitive Zero</a></li>
        <li><a href="../tokens.html">Tokens</a></li>
        <li><a href="../layouts.html">Layouts</a></li>
      </ul>

      <h3>Primitives</h3>
      <ul>
        <li><a href="ren-button.html">Button</a></li>
        <li><a href="ren-card.html">Card</a></li>
        <li><a href="ren-badge.html">Badge</a></li>
        <li><a href="ren-tag.html">Tag</a></li>
        <li><a href="ren-link.html">Link</a></li>
        <li><a href="ren-banner.html">Banner</a></li>
        <li><a href="ren-breadcrumb.html">Breadcrumb</a></li>
        <li><a href="ren-pagination.html">Pagination</a></li>
        <li><a href="ren-separator.html">Separator</a></li>
        <li><a href="ren-avatar.html">Avatar</a></li>
        <li><a href="ren-spinner.html">Spinner</a></li>
        <li><a href="ren-skeleton.html">Skeleton</a></li>
        <li><a href="ren-kbd.html">Keyboard Key</a></li>
        <li><a href="ren-icon.html">Icons</a></li>
        <li><a href="ren-field.html">Field</a></li>
        <li><a href="ren-checkbox.html">Checkbox</a></li>
        <li><a href="ren-switch.html">Switch</a></li>
        <li><a href="ren-radio.html">Radio</a></li>
        <li><a href="ren-progress.html">Progress</a></li>
      </ul>

      <h3>Composites</h3>
      <ul>
        <li><a href="ren-tabs.html">Tabs</a></li>
        <li><a href="ren-accordion.html" aria-current="page">Accordion</a></li>
        <li><a href="ren-dialog.html">Dialog</a></li>
        <li><a href="ren-alert-dialog.html">Alert Dialog</a></li>
        <li><a href="ren-toast.html">Toast</a></li>
        <li><a href="ren-tooltip.html">Tooltip</a></li>
        <li><a href="ren-popover.html">Popover</a></li>
        <li><a href="ren-hover-card.html">Hover Card</a></li>
        <li><a href="ren-sheet.html">Sheet</a></li>
        <li><a href="ren-collapsible.html">Collapsible</a></li>
        <li><a href="ren-toolbar.html">Toolbar</a></li>
        <li><a href="ren-dropzone.html">Dropzone</a></li>
        <li><a href="ren-combobox.html">Combobox</a></li>
        <li><a href="ren-slider.html">Slider</a></li>
        <li><a href="ren-toggle-group.html">Toggle Group</a></li>
        <li><a href="ren-scroll-area.html">Scroll Area</a></li>
        <li><a href="ren-select.html">Select</a></li>
        <li><a href="ren-menu.html">Menu</a></li>
        <li><a href="ren-menubar.html">Menubar</a></li>
        <li><a href="ren-context-menu.html">Context Menu</a></li>
        <li><a href="ren-command.html">Command Palette</a></li>
        <li><a href="ren-number-field.html">Number Field</a></li>
        <li><a href="ren-otp.html">Input OTP</a></li>
        <li><a href="ren-color-picker.html">Color Picker</a></li>
        <li><a href="ren-calendar.html">Calendar</a></li>
        <li><a href="ren-date-picker.html">Date Picker</a></li>
        <li><a href="ren-date-range-picker.html">Date Range Picker</a></li>
        <li><a href="ren-carousel.html">Carousel</a></li>
      </ul>

      <h3>Patterns</h3>
      <ul>
        <li><a href="ren-nav.html">Nav</a></li>
        <li><a href="ren-sidebar.html">Sidebar</a></li>
        <li><a href="ren-empty-state.html">Empty State</a></li>
        <li><a href="ren-table.html">Data Table</a></li>
        <li><a href="ren-form.html">Form Validation</a></li>
        <li><a href="ren-ai.html">AI Patterns</a></li>
      </ul>

      <h3>Reference</h3>
      <ul>
        <li><a href="../components.html">Components catalog</a></li>
</ul>
    </aside>

    <main class="dx-content">
      <header class="dx-header">
        <nav class="ren-breadcrumb" aria-label="Breadcrumb" style="margin-bottom: var(--space-4);"><ol><li><a href="../index.html" class="ren-link-plain">Docs</a></li><li><a href="../components.html" class="ren-link-plain">Components</a></li><li aria-current="page">Accordion</li></ol></nav>
        <p class="dx-kicker">Composite</p>
        <h1>Accordion <span class="dx-api-badge dx-api-badge-hybrid" title="Renders without JS; JS adds enhancements">Hybrid</span></h1>
        <p class="lede">Stack of collapsible panels. Built on the native <code>&lt;details&gt;</code> + <code>&lt;summary&gt;</code> pair so accessibility comes for free. Single-open or multi-open mode, animated expand / collapse.</p>
      </header>

      <section class="dx-section" id="overview">
        <p class="dx-kicker">About</p>
        <h2>Overview</h2>
        <p>Accordions hide secondary content behind disclosure triggers. Use them when the page has more sections than fits on a screen and most users only need one or two at a time — FAQs, settings groups, app feature tours. RenDS just styles the native <code>&lt;details&gt;</code>; the platform does the open/close logic, keyboard handling, and the <code>open</code> attribute.</p>
        <div class="dx-callout">
          <p><strong>Don't hide critical content in an accordion.</strong> If users need to read it to make a decision, show it. Accordions hide content one level removed from the primary task.</p>
        </div>
      </section>

      <section class="dx-section" id="demo">
        <p class="dx-kicker">Live</p>
        <h2>Demo</h2>
        <div class="dx-demo">
          <div class="dx-demo-preview">
            <div class="demo-acc" style="max-width: 520px;">
              <details open><summary>How does pricing work?</summary><div class="demo-acc-body">We charge per user, per month. The Basic plan is $9; Pro is $29 and removes seat caps. Annual billing saves 20%.</div></details>
              <details><summary>Can I cancel anytime?</summary><div class="demo-acc-body">Yes — cancel from your billing settings. Your plan stays active until the end of the current period; we don't pro-rate refunds.</div></details>
              <details><summary>Do you offer a free trial?</summary><div class="demo-acc-body">14 days, no credit card. After the trial you'll be downgraded to the free tier unless you pick a plan.</div></details>
            </div>
          </div>
          <pre class="dx-pre dx-demo-code" tabindex="0"><code>&lt;ren-accordion&gt;
  &lt;details open&gt;
    &lt;summary&gt;How does pricing work?&lt;/summary&gt;
    &lt;p&gt;We charge per user, per month…&lt;/p&gt;
  &lt;/details&gt;
  &lt;details&gt;
    &lt;summary&gt;Can I cancel anytime?&lt;/summary&gt;
    &lt;p&gt;Yes — cancel from your billing settings…&lt;/p&gt;
  &lt;/details&gt;
&lt;/ren-accordion&gt;</code></pre>
        </div>
      </section>

      <section class="dx-section" id="api">
        <p class="dx-kicker">Reference</p>
        <h2>API</h2>
        <table class="dx-api">
          <thead><tr><th>Class</th><th>Effect</th></tr></thead>
          <tbody>
            <tr><td><code class="dx-api-name">.ren-accordion</code></td><td>Stack container. Borders the group; visually links the items.</td></tr>
            <tr><td><code class="dx-api-name">.ren-accordion-item</code></td><td>Optional class on each <code>&lt;details&gt;</code>. Useful when accordions live without the wrapper.</td></tr>
          </tbody>
        </table>
        <h3>Web Component attributes</h3>
        <table class="dx-api dx-api-cols-4">
          <thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>
          <tbody>
            <tr><td><code class="dx-api-name">single</code></td><td><span class="dx-api-type">boolean</span></td><td><span class="dx-api-default">false</span></td><td>If present, opening one item closes the others (mutually exclusive mode).</td></tr>
          </tbody>
        </table>
      </section>

      <section class="dx-section" id="a11y">
        <p class="dx-kicker">Inclusive by default</p>
        <h2>Accessibility</h2>
        <p>Native <code>&lt;details&gt;</code> handles all the accessibility — keyboard, screen reader announcement of expanded state, the <code>aria-expanded</code> wiring. You don't need to add anything.</p>
        <ul>
          <li><kbd>Tab</kbd> moves focus to a closed <code>&lt;summary&gt;</code>; <kbd>Enter</kbd> or <kbd>Space</kbd> toggles open / closed.</li>
          <li>Screen readers announce "expanded" or "collapsed" automatically.</li>
          <li>The summary is the trigger — keep it short and start with a verb or noun the user is looking for.</li>
        </ul>
      </section>

    </main>
  </div>
  <script src="../../site/shell.js" defer></script>
</body>
</html>


id: docs:docs/components/ren-ai.html
type: docs_page
name: ren-ai docs
path: docs/components/ren-ai.html
data: {}
body:
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI Patterns — RenDS Components</title>
  <link rel="stylesheet" href="../../index.css">
  <link rel="stylesheet" href="../../components/index.css">
  <link rel="stylesheet" href="../../themes/appearance.css">
  <link rel="stylesheet" href="../../tokens/component/tokens.css">
  <link rel="stylesheet" href="../../site/shell.css">
  <style>
    .dx-demo { border: 1px solid var(--color-border); border-radius: var(--radius-md); overflow: hidden; margin: var(--space-4) 0; }
    .dx-demo-preview { background: var(--color-surface-raised); padding: var(--space-6) var(--space-5); }
    .dx-demo-code   { margin: 0; border-radius: 0; border: none; border-top: 1px solid var(--color-border); }

    .dx-vgrid { display: grid; gap: var(--space-6); margin: var(--space-4) 0; }
    .dx-vrow  { display: grid; grid-template-columns: 140px 1fr; gap: var(--space-4); align-items: start; }
    .dx-vrow-label {
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      text-transform: uppercase;
      letter-spacing: 0.06em;
      color: var(--color-text-muted);
      padding-top: var(--space-3);
    }
    .dx-vrow-items {
      padding: var(--space-3);
      background: var(--color-surface-raised);
      border: 1px solid var(--color-border);
      border-radius: var(--radius-md);
      display: grid;
      gap: var(--space-3);
    }
    @media (max-width: 720px) {
      .dx-vrow { grid-template-columns: 1fr; gap: var(--space-2); }
      .dx-vrow-label { padding-top: 0; }
    }
  </style>
</head>
<body>
  <header class="dx-nav">
    <div class="dx-nav-inner">
      <a href="../index.html" class="dx-brand"><span class="dx-brand-mark">R</span><span>RenDS</span><span class="ren-badge ren-badge-secondary" style="margin-left: var(--space-1);">v0.12.0</span></a>
      <nav class="dx-nav-menu" aria-label="Primary">
        <a href="../index.html">Docs</a>
        <a href="../components.html" aria-current="page">Components</a>
        <a href="../../templates/index.html">Templates</a>
        <a href="../../create/index.html">Theme Builder</a>
      </nav>
      <div class="dx-nav-actions">
        <a href="https://github.com/Rensoconese/ren10" class="ren-btn ren-btn-ghost ren-btn-sm">GitHub</a>
        <a href="../getting-started.html" class="ren-btn ren-btn-primary ren-btn-sm">Get started</a>
      </div>
    </div>
  </header>

  <div class="dx-shell dx-shell-grid">

    <aside class="dx-sidebar" aria-label="Site navigation">
      <h3>Guides</h3>
      <ul>
        <li><a href="../getting-started.html">Getting Started</a></li>
        <li><a href="../theming.html">Theming</a></li>
        <li><a href="../accessibility.html">Accessibility</a></li>
        <li><a href="../cli.html">CLI</a></li>
      </ul>

      <h3>Foundations</h3>
      <ul>
        <li><a href="../primitive-zero.html">Primitive Zero</a></li>
        <li><a href="../tokens.html">Tokens</a></li>
        <li><a href="../layouts.html">Layouts</a></li>
      </ul>

      <h3>Primitives</h3>
      <ul>
        <li><a href="ren-button.html">Button</a></li>
        <li><a href="ren-card.html">Card</a></li>
        <li><a href="ren-badge.html">Badge</a></li>
        <li><a href="ren-tag.html">Tag</a></li>
        <li><a href="ren-link.html">Link</a></li>
        <li><a href="ren-banner.html">Banner</a></li>
        <li><a href="ren-breadcrumb.html">Breadcrumb</a></li>
        <li><a href="ren-pagination.html">Pagination</a></li>
        <li><a href="ren-separator.html">Separator</a></li>
        <li><a href="ren-avatar.html">Avatar</a></li>
        <li><a href="ren-spinner.html">Spinner</a></li>
        <li><a href="ren-skeleton.html">Skeleton</a></li>
        <li><a href="ren-kbd.html">Keyboard Key</a></li>
        <li><a href="ren-icon.html">Icons</a></li>
        <li><a href="ren-field.html">Field</a></li>
        <li><a href="ren-checkbox.html">Checkbox</a></li>
        <li><a href="ren-switch.html">Switch</a></li>
        <li><a href="ren-radio.html">Radio</a></li>
        <li><a href="ren-progress.html">Progress</a></li>
      </ul>

      <h3>Composites</h3>
      <ul>
        <li><a href="ren-tabs.html">Tabs</a></li>
        <li><a href="ren-accordion.html">Accordion</a></li>
        <li><a href="ren-dialog.html">Dialog</a></li>
        <li><a href="ren-alert-dialog.html">Alert Dialog</a></li>
        <li><a href="ren-toast.html">Toast</a></li>
        <li><a href="ren-tooltip.html">Tooltip</a></li>
        <li><a href="ren-popover.html">Popover</a></li>
        <li><a href="ren-hover-card.html">Hover Card</a></li>
        <li><a href="ren-sheet.html">Sheet</a></li>
        <li><a href="ren-collapsible.html">Collapsible</a></li>
        <li><a href="ren-toolbar.html">Toolbar</a></li>
        <li><a href="ren-dropzone.html">Dropzone</a></li>
        <li><a href="ren-combobox.html">Combobox</a></li>
        <li><a href="ren-slider.html">Slider</a></li>
        <li><a href="ren-toggle-group.html">Toggle Group</a></li>
        <li><a href="ren-scroll-area.html">Scroll Area</a></li>
        <li><a href="ren-select.html">Select</a></li>
        <li><a href="ren-menu.html">Menu</a></li>
        <li><a href="ren-menubar.html">Menubar</a></li>
        <li><a href="ren-context-menu.html">Context Menu</a></li>
        <li><a href="ren-command.html">Command Palette</a></li>
        <li><a href="ren-number-field.html">Number Field</a></li>
        <li><a href="ren-otp.html">Input OTP</a></li>
        <li><a href="ren-color-picker.html">Color Picker</a></li>
        <li><a href="ren-calendar.html">Calendar</a></li>
        <li><a href="ren-date-picker.html">Date Picker</a></li>
        <li><a href="ren-date-range-picker.html">Date Range Picker</a></li>
        <li><a href="ren-carousel.html">Carousel</a></li>
      </ul>

      <h3>Patterns</h3>
      <ul>
        <li><a href="ren-nav.html">Nav</a></li>
        <li><a href="ren-sidebar.html">Sidebar</a></li>
        <li><a href="ren-empty-state.html">Empty State</a></li>
        <li><a href="ren-table.html">Data Table</a></li>
        <li><a href="ren-form.html">Form Validation</a></li>
        <li><a href="ren-ai.html" aria-current="page">AI Patterns</a></li>
      </ul>

      <h3>Reference</h3>
      <ul>
        <li><a href="../components.html">Components catalog</a></li>
      </ul>
    </aside>

    <main class="dx-content">
      <header class="dx-header">
        <nav class="ren-breadcrumb" aria-label="Breadcrumb" style="margin-bottom: var(--space-4);"><ol><li><a href="../index.html" class="ren-link-plain">Docs</a></li><li><a href="../components.html" class="ren-link-plain">Components</a></li><li aria-current="page">AI Patterns</li></ol></nav>
        <p class="dx-kicker">Pattern</p>
        <h1>AI Patterns <span class="dx-api-badge dx-api-badge-css" title="Works without any JavaScript">CSS-only</span></h1>
        <p class="lede"><strong>CSS-only pattern. No Web Component, no colocated JS — bring your own inference layer.</strong> Visual primitives for chat-style AI surfaces: assistant message bubbles, streaming + typing indicators, inline citations, source lists, confidence bars, prompt input, and action affordances (copy, regenerate, thumbs).</p>
      </header>

      <!-- 1. OVERVIEW -->
      <section class="dx-section" id="overview">
        <p class="dx-kicker">About</p>
        <h2>Overview</h2>
        <p>AI chat surfaces share a small visual vocabulary: a thread of assistant messages, a streaming response indicator while the model is thinking, inline citations linking to source documents, an action row under the latest message (copy, regenerate, thumbs up/down), and a prompt input at the bottom. RenDS provides every one of those as a stand-alone CSS primitive you can compose freely.</p>
        <p><strong>Important: this is a CSS-only pattern.</strong> There is no <code>&lt;ren-ai&gt;</code> custom element, no JavaScript file that ships with the package, and no streaming or state machine baked in. You bring:</p>
        <ul>
          <li>The inference call (OpenAI, Anthropic, your own backend, etc.).</li>
          <li>The streaming text update logic (append tokens to <code>.ren-ai-message-content</code>).</li>
          <li>The action handlers (copy → clipboard API, thumbs → your feedback endpoint, regenerate → re-call the model).</li>
          <li>The thread state (which message is streaming, message history, scroll-to-bottom).</li>
        </ul>
        <p>RenDS does the visual + accessibility plumbing — tokens, layout, focus rings, live regions, reduced-motion fallbacks — so your application code focuses on the model logic, not the chrome.</p>

        <h3>When to use</h3>
        <ul>
          <li>Building a chat-style assistant UI (in-app help, support agent, document Q&A).</li>
          <li>Adding an "Ask AI" panel next to existing content.</li>
          <li>Rendering AI-generated text with provenance (citations + sources).</li>
        </ul>

        <h3>When not to use</h3>
        <ul>
          <li><strong>Static text labeled "AI-generated".</strong> Use a <a href="ren-badge.html" class="ren-link">Badge</a> or the <code>.ren-ai-slug</code> primitive inline instead.</li>
          <li><strong>Form-style "AI assist".</strong> If the AI just fills a field, use <a href="ren-field.html" class="ren-link">Field</a> with a custom button — no chat shell needed.</li>
          <li><strong>Real-time co-edit / multiplayer.</strong> Out of scope — this pattern assumes turn-based request → response.</li>
        </ul>

        <div class="dx-callout">
          <p><strong>Visual primitives, not a chat engine.</strong> If you need a higher-level component that manages thread state, streaming, and message history, build it on top using the classes here. The intent is to give you the look without imposing a state shape.</p>
        </div>
      </section>

      <!-- 2. ANATOMY -->
      <section class="dx-section" id="anatomy">
        <p class="dx-kicker">Structure</p>
        <h2>Anatomy</h2>
        <p>A complete AI surface has six possible regions. Pick what you need.</p>
        <ol>
          <li><strong>AI slug</strong> (<code>.ren-ai-slug</code>) — inline label marking content as AI-generated. Place wherever you'd write "AI" in text.</li>
          <li><strong>Message</strong> (<code>.ren-ai-message</code>) — assistant bubble. Header (avatar / title), content (the text), footer (actions). User messages style themselves — keep them as plain bubbles or use your own.</li>
          <li><strong>Indicators</strong> (<code>.ren-ai-typing</code>, <code>.ren-ai-streaming</code>) — shown while the model is generating. The typing dots show "thinking"; streaming pulses the latest token.</li>
          <li><strong>Citations + sources</strong> (<code>.ren-ai-citation</code>, <code>.ren-ai-sources</code>) — superscript references in the body that link to a list of sources rendered below the message.</li>
          <li><strong>Confidence</strong> (<code>.ren-ai-confidence</code>) — optional bar showing model confidence (data-level="high|medium|low").</li>
          <li><strong>Prompt</strong> (<code>.ren-ai-prompt</code>) — the input at the bottom of the thread with a send button.</li>
        </ol>
      </section>

      <!-- 3. DEMO -->
      <section class="dx-section" id="demo">
        <p class="dx-kicker">Live</p>
        <h2>Demo</h2>
        <div class="dx-demo">
          <div class="dx-demo-preview">
            <div role="log" aria-live="polite" aria-label="Conversation with assistant" style="display: grid; gap: var(--space-3); max-width: 580px; margin-inline: auto;">

              <!-- user turn -->
              <div style="justify-self: end; background: var(--color-accent); color: var(--color-on-accent); padding: var(--space-3) var(--space-4); border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm) var(--radius-lg); max-width: 80%; font-size: var(--text-sm);">
                What's the difference between a Tooltip and a Popover?
              </div>

              <!-- assistant turn with full chrome -->
              <div class="ren-ai-message">
                <header class="ren-ai-message-header">
                  <span class="ren-ai-slug">AI</span>
                  <span style="color: var(--color-text-muted); font-size: var(--text-xs);">Assistant · just now</span>
                </header>
                <div class="ren-ai-message-content">
                  Tooltip is a passive hint shown on hover or focus — typically just text<sup class="ren-ai-citation">1</sup>.
                  Popover is interactive: it can hold forms, multiple actions, anything keyboard-navigable<sup class="ren-ai-citation">2</sup>.
                  Tooltip never traps focus; Popover does.
                </div>

                <div class="ren-ai-confidence" data-level="high">
                  <span style="font-size: var(--text-xs); color: var(--color-text-muted);">Confidence: High</span>
                  <div class="ren-ai-confidence-bar"><div class="ren-ai-confidence-fill"></div></div>
                </div>

                <ol class="ren-ai-sources">
                  <li class="ren-ai-source">
                    <span class="ren-ai-source-number">1</span>
                    <a class="ren-ai-source-title" href="#">Tooltip — RenDS docs</a>
                    <span class="ren-ai-source-url">rends.dev/docs/components/ren-tooltip.html</span>
                  </li>
                  <li class="ren-ai-source">
                    <span class="ren-ai-source-number">2</span>
                    <a class="ren-ai-source-title" href="#">Popover — RenDS docs</a>
                    <span class="ren-ai-source-url">rends.dev/docs/components/ren-popover.html</span>
                  </li>
                </ol>

                <footer class="ren-ai-message-footer">
                  <div class="ren-ai-actions">
                    <button type="button" class="ren-ai-action" aria-label="Copy response">📋 Copy</button>
                    <button type="button" class="ren-ai-action" aria-label="Regenerate response">↻ Regenerate</button>
                    <button type="button" class="ren-ai-action ren-ai-action-thumbs" aria-label="Helpful">👍</button>
                    <button type="button" class="ren-ai-action ren-ai-action-thumbs" aria-label="Not helpful">👎</button>
                  </div>
                </footer>
              </div>

              <!-- typing indicator -->
              <div class="ren-ai-typing" aria-live="polite" aria-label="Assistant is typing">
                <span class="ren-ai-typing-dots"><span></span><span></span><span></span></span>
                <span style="font-size: var(--text-xs); color: var(--color-text-muted);">Thinking…</span>
              </div>
            </div>
          </div>
          <pre class="dx-pre dx-demo-code" tabindex="0"><code>&lt;div role="log" aria-live="polite" aria-label="Conversation with assistant"&gt;

  &lt;!-- assistant message --&gt;
  &lt;div class="ren-ai-message"&gt;
    &lt;header class="ren-ai-message-header"&gt;
      &lt;span class="ren-ai-slug"&gt;AI&lt;/span&gt;
      &lt;span&gt;Assistant · just now&lt;/span&gt;
    &lt;/header&gt;

    &lt;div class="ren-ai-message-content"&gt;
      Tooltip is passive&lt;sup class="ren-ai-citation"&gt;1&lt;/sup&gt;.
      Popover is interactive&lt;sup class="ren-ai-citation"&gt;2&lt;/sup&gt;.
    &lt;/div&gt;

    &lt;div class="ren-ai-confidence" data-level="high"&gt;
      &lt;span&gt;Confidence: High&lt;/span&gt;
      &lt;div class="ren-ai-confidence-bar"&gt;
        &lt;div class="ren-ai-confidence-fill"&gt;&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;ol class="ren-ai-sources"&gt;
      &lt;li class="ren-ai-source"&gt;
        &lt;span class="ren-ai-source-number"&gt;1&lt;/span&gt;
        &lt;a class="ren-ai-source-title" href="…"&gt;Tooltip — RenDS docs&lt;/a&gt;
        &lt;span class="ren-ai-source-url"&gt;rends.dev/…&lt;/span&gt;
      &lt;/li&gt;
    &lt;/ol&gt;

    &lt;footer class="ren-ai-message-footer"&gt;
      &lt;div class="ren-ai-actions"&gt;
        &lt;button class="ren-ai-action"&gt;📋 Copy&lt;/button&gt;
        &lt;button class="ren-ai-action"&gt;↻ Regenerate&lt;/button&gt;
        &lt;button class="ren-ai-action ren-ai-action-thumbs"&gt;👍&lt;/button&gt;
      &lt;/div&gt;
    &lt;/footer&gt;
  &lt;/div&gt;

  &lt;!-- typing indicator (while model is generating) --&gt;
  &lt;div class="ren-ai-typing" aria-live="polite"&gt;
    &lt;span class="ren-ai-typing-dots"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span&gt;Thinking…&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
        </div>
      </section>

      <!-- 4. VARIANTS -->
      <section class="dx-section" id="variants">
        <p class="dx-kicker">Visual</p>
        <h2>Variants</h2>

        <div class="dx-vgrid">

          <div class="dx-vrow">
            <div class="dx-vrow-label">AI slug</div>
            <div class="dx-vrow-items">
              <p>Inline marker that flags AI-generated content. Wear it on a button, in copy, or on a card.</p>
              <p style="margin: 0;">This text was <span class="ren-ai-slug">AI</span> generated.</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;span class="ren-ai-slug"&gt;AI&lt;/span&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Streaming</div>
            <div class="dx-vrow-items">
              <p>Add <code>.ren-ai-streaming</code> on the message bubble while tokens are being appended to <code>.ren-ai-message-content</code>. It animates the trailing edge as a "still talking" cue.</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;div class="ren-ai-message ren-ai-streaming"&gt;
  &lt;div class="ren-ai-message-content"&gt;The capital of France is Pa&lt;/div&gt;
&lt;/div&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Typing</div>
            <div class="dx-vrow-items">
              <p>Pre-streaming "thinking" state. Three pulsing dots inside <code>.ren-ai-typing-dots</code>.</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;div class="ren-ai-typing" aria-live="polite"&gt;
  &lt;span class="ren-ai-typing-dots"&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;Thinking…&lt;/span&gt;
&lt;/div&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Skeleton</div>
            <div class="dx-vrow-items">
              <p>Placeholder for a message that hasn't started streaming yet (e.g. initial load of a saved thread).</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;div class="ren-ai-message"&gt;
  &lt;div class="ren-ai-skeleton"&gt;
    &lt;div class="ren-ai-skeleton-line"&gt;&lt;/div&gt;
    &lt;div class="ren-ai-skeleton-line"&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Confidence</div>
            <div class="dx-vrow-items">
              <p>Three levels via <code>data-level</code>. The bar fills proportionally and color-codes (high → success, medium → warning, low → muted).</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;div class="ren-ai-confidence" data-level="high|medium|low"&gt;
  &lt;span&gt;Confidence: High&lt;/span&gt;
  &lt;div class="ren-ai-confidence-bar"&gt;
    &lt;div class="ren-ai-confidence-fill"&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Citations</div>
            <div class="dx-vrow-items">
              <p>Inline <code>&lt;sup class="ren-ai-citation"&gt;</code> references that link to the sources list under the message.</p>
              <pre class="dx-pre" tabindex="0"><code>The capital is Paris&lt;sup class="ren-ai-citation"&gt;1&lt;/sup&gt;.

&lt;ol class="ren-ai-sources"&gt;
  &lt;li class="ren-ai-source"&gt;
    &lt;span class="ren-ai-source-number"&gt;1&lt;/span&gt;
    &lt;a class="ren-ai-source-title" href="…"&gt;World Atlas, 2024&lt;/a&gt;
    &lt;span class="ren-ai-source-url"&gt;example.com/atlas&lt;/span&gt;
  &lt;/li&gt;
&lt;/ol&gt;</code></pre>
            </div>
          </div>

          <div class="dx-vrow">
            <div class="dx-vrow-label">Prompt input</div>
            <div class="dx-vrow-items">
              <p>Sticky composer at the bottom of the thread. Plain <code>&lt;textarea&gt;</code> + send <code>&lt;button&gt;</code> wrapped in <code>.ren-ai-prompt</code>.</p>
              <pre class="dx-pre" tabindex="0"><code>&lt;form class="ren-ai-prompt" action="/api/chat" method="post"&gt;
  &lt;textarea class="ren-ai-prompt-input" name="prompt"
            placeholder="Ask anything…"
            rows="1"&gt;&lt;/textarea&gt;
  &lt;button type="submit" class="ren-ai-prompt-send" aria-label="Send"&gt;→&lt;/button&gt;
&lt;/form&gt;</code></pre>
            </div>
          </div>

        </div>
      </section>

      <!-- 5. API -->
      <section class="dx-section" id="api">
        <p class="dx-kicker">Reference</p>
        <h2>API</h2>

        <h3>CSS classes</h3>
        <table class="dx-api">
          <thead><tr><th>Class</th><th>Effect</th></tr></thead>
          <tbody>
            <tr><td><code class="dx-api-name">.ren-ai-slug</code></td><td>Inline "AI" pill. Use anywhere AI-generated content needs an explicit marker.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-message</code></td><td>Assistant message bubble. Wraps header / content / footer.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-message-header</code></td><td>Top row inside the bubble: slug, author name, timestamp.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-message-content</code></td><td>The body text. Append streaming tokens here.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-message-footer</code></td><td>Bottom row: confidence, sources, actions.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-streaming</code></td><td>Add to <code>.ren-ai-message</code> while tokens are streaming. Animates the trailing edge.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-typing</code></td><td>Pre-streaming "thinking" container. Sibling, not nested, of the streaming message.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-typing-dots</code></td><td>Three pulsing dots inside <code>.ren-ai-typing</code>.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-skeleton</code> / <code class="dx-api-name">.ren-ai-skeleton-line</code></td><td>Placeholder for a not-yet-loaded message.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-citation</code></td><td>On <code>&lt;sup&gt;</code> inline in content. Renders as a clickable reference number.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-sources</code></td><td>Ordered list of source entries below the message body.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-source</code></td><td>One source list item. Number + title + url.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-source-number</code> / <code class="dx-api-name">-title</code> / <code class="dx-api-name">-url</code></td><td>Composable parts of a source entry.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-confidence</code></td><td>Confidence indicator. Set <code>data-level="high" | "medium" | "low"</code>.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-confidence-bar</code> / <code class="dx-api-name">-fill</code></td><td>Track + filled portion of the bar.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-actions</code></td><td>Trailing row of action buttons under a message.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-action</code></td><td>One action button (copy, regenerate, etc.). Real <code>&lt;button&gt;</code>.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-action-thumbs</code></td><td>Variant for thumbs up / down feedback buttons.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-prompt</code></td><td>Wrapper for the composer at the bottom of the thread.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-prompt-input</code></td><td>The <code>&lt;textarea&gt;</code> styled to grow with content.</td></tr>
            <tr><td><code class="dx-api-name">.ren-ai-prompt-send</code></td><td>The send button. Icon-only by convention.</td></tr>
          </tbody>
        </table>

        <h3>Data attributes</h3>
        <table class="dx-api">
          <thead><tr><th>Attribute</th><th>Where</th><th>Effect</th></tr></thead>
          <tbody>
            <tr><td><code class="dx-api-name">data-level</code></td><td><code>.ren-ai-confidence</code></td><td><code>high</code> / <code>medium</code> / <code>low</code>. Tints the bar.</td></tr>
          </tbody>
        </table>

        <h3>JavaScript</h3>
        <p>None ships with this pattern. The customer code:</p>
        <ul>
          <li>Appends tokens to <code>.ren-ai-message-content</code> during streaming.</li>
          <li>Adds / removes <code>.ren-ai-streaming</code> to flip the trailing-edge animation.</li>
          <li>Wires <code>.ren-ai-action</code> click handlers (clipboard for copy, callback for regenerate / thumbs).</li>
          <li>Manages the thread state (which message is active, scroll-to-bottom, save history).</li>
          <li>Handles the <code>.ren-ai-prompt</code> form submission (call the model, render the response).</li>
        </ul>

        <div class="dx-callout">
          <p><strong>Coming in 0.9.0:</strong> selectors for error messages (<code>.ren-ai-error</code>), file upload chips (<code>.ren-ai-file-chip</code>), tool-call display (<code>.ren-ai-tool-call</code>), and edit-and-resend (<code>.ren-ai-action-edit</code>). Still CSS-only — same philosophy.</p>
        </div>
      </section>

      <!-- 6. ACCESSIBILITY -->
      <section class="dx-section" id="a11y">
        <p class="dx-kicker">Inclusive by default</p>
        <h2>Accessibility</h2>

        <h3>Live regions</h3>
        <ul>
          <li>Wrap the thread in <code>role="log"</code> + <code>aria-live="polite"</code>. New messages are announced as they appear.</li>
          <li>For long streaming responses, <strong>announce the final text once when complete</strong>, not character by character. Strategy: render the streaming text inside an off-live container, then move it into a live region when streaming finishes.</li>
          <li>The typing indicator carries its own <code>aria-live="polite"</code> + <code>aria-label="Assistant is typing"</code> so it's announced once.</li>
        </ul>

        <h3>Focus management</h3>
        <ul>
          <li><strong>Don't auto-scroll while the user is reading.</strong> Pause auto-scroll when the user scrolls up from the bottom; resume when they reach the bottom again.</li>
          <li><strong>Don't move focus on each new message</strong> — focus stays where the user put it.</li>
          <li><strong>Do</strong> move focus to the prompt input after submitting (the user is likely typing the next message).</li>
        </ul>

        <h3>Action buttons</h3>
        <ul>
          <li>Always include an <strong>accessible name</strong> on icon-only actions (<code>aria-label="Copy response"</code>, <code>aria-label="Regenerate"</code>).</li>
          <li>Toggle state on thumbs: <code>aria-pressed="true"</code> when the user clicked it.</li>
          <li>"Copy" is the most-used affordance — make sure it works with no extra modal.</li>
        </ul>

        <h3>Citations + sources</h3>
        <ul>
          <li>The citation <code>&lt;sup&gt;</code> should be a real <code>&lt;a href="#source-N"&gt;</code> so keyboard / screen-reader users can jump to the source list.</li>
          <li>Source URLs should be readable to screen readers — don't replace the entire URL with an icon.</li>
        </ul>

        <h3>Reduced motion</h3>
        <p>All animations (streaming pulse, typing dots, skeleton shimmer) respect <code>prefers-reduced-motion: reduce</code> and fall back to static / opacity-only states. You don't have to wire this — the CSS handles it.</p>

        <div class="dx-callout">
          <p><strong>Always provide a way out.</strong> An AI thread without a "stop generating" button traps the user when the model goes off the rails. Include a cancel affordance — RenDS doesn't enforce it but it's the most important AI-UX rule.</p>
        </div>
      </section>

      <!-- 7. EXAMPLES -->
      <section class="dx-section" id="examples">
        <p class="dx-kicker">Patterns</p>
        <h2>Examples</h2>

        <h3>Streaming a response</h3>
        <p>Render an empty <code>.ren-ai-message</code> with <code>.ren-ai-streaming</code> while you append tokens to <code>.ren-ai-message-content</code>.</p>
        <pre class="dx-pre" tabindex="0"><code>const msg = document.createElement('div');
msg.className = 'ren-ai-message ren-ai-streaming';
msg.innerHTML = `
  &lt;header class="ren-ai-message-header"&gt;
    &lt;span class="ren-ai-slug"&gt;AI&lt;/span&gt;
  &lt;/header&gt;
  &lt;div class="ren-ai-message-content"&gt;&lt;/div&gt;
`;
thread.append(msg);
const target = msg.querySelector('.ren-ai-message-content');

for await (const chunk of streamFromModel(prompt)) {
  target.append(chunk);
}

// done streaming
msg.classList.remove('ren-ai-streaming');</code></pre>

        <h3>Copy action with clipboard API</h3>
        <pre class="dx-pre" tabindex="0"><code>thread.addEventListener('click', async (e) =&gt; {
  const btn = e.target.closest('.ren-ai-action');
  if (!btn || btn.dataset.action !== 'copy') return;

  const msg = btn.closest('.ren-ai-message');
  const text = msg.querySelector('.ren-ai-message-content').innerText;
  await navigator.clipboard.writeText(text);
  toast.success('Copied');
});</code></pre>

        <h3>Thumbs feedback with aria-pressed</h3>
        <pre class="dx-pre" tabindex="0"><code>// Markup
&lt;button type="button"
        class="ren-ai-action ren-ai-action-thumbs"
        aria-pressed="false"
        aria-label="Helpful"&gt;👍&lt;/button&gt;

// JS
btn.addEventListener('click', () =&gt; {
  const pressed = btn.getAttribute('aria-pressed') === 'true';
  btn.setAttribute('aria-pressed', String(!pressed));
  fetch('/api/feedback', {
    method: 'POST',
    body: JSON.stringify({ messageId, helpful: !pressed }),
  });
});</code></pre>

        <h3>Suggestion chips</h3>
        <p>Use <a href="ren-tag.html" class="ren-link">Tag</a> with the <code>clickable</code> variant — there's no AI-specific suggestion class, suggestions are just tags.</p>
        <pre class="dx-pre" tabindex="0"><code>&lt;div style="display: flex; flex-wrap: wrap; gap: var(--space-1);"&gt;
  &lt;button class="ren-tag ren-tag-clickable"&gt;Show me an example&lt;/button&gt;
  &lt;button class="ren-tag ren-tag-clickable"&gt;What about Dialog?&lt;/button&gt;
&lt;/div&gt;</code></pre>
      </section>

    </main>
  </div>

  <script src="../../site/shell.js" defer></script>
</body>
</html>


