# Components

> The component set as live specimens — buttons, inputs, cards, tables, navigation, feedback, overlays, and data display.


Every specimen on this page is live markup styled by the brand tokens, ported
from the supplied kitchen-sink mockup. They follow your colour mode —
switch the theme and they change with the documentation.

Measurements listed here are normative: a 40px input is 40px.

## Buttons

Five variants, three sizes. Plus Jakarta Sans 600, 6px radius, 200ms transitions.

{{< demo label="Variants — medium (40px)" >}}
<button class="pj-btn pj-btn--primary pj-btn--md">Get started</button>
<button class="pj-btn pj-btn--accent pj-btn--md">Deploy</button>
<button class="pj-btn pj-btn--outline pj-btn--md">Learn more</button>
<button class="pj-btn pj-btn--ghost pj-btn--md">Cancel</button>
<button class="pj-btn pj-btn--danger pj-btn--md">Delete</button>
<button class="pj-btn pj-btn--primary pj-btn--md" disabled>Disabled</button>
{{< /demo >}}

{{< demo label="Sizes — sm 32px · md 40px · lg 48px" >}}
<button class="pj-btn pj-btn--accent pj-btn--sm">Small</button>
<button class="pj-btn pj-btn--accent pj-btn--md">Medium</button>
<button class="pj-btn pj-btn--accent pj-btn--lg">Large</button>
{{< /demo >}}

{{< demo label="On a midnight surface" variant="dark" >}}
<button class="pj-btn pj-btn--accent pj-btn--md">Deploy</button>
<button class="pj-btn pj-btn--outline pj-btn--md">View logs</button>
<button class="pj-btn pj-btn--ghost pj-btn--md">Dismiss</button>
{{< /demo >}}

| Variant | Fill | Border | Text | Contrast | Use |
|---|---|---|---|---|---|
| **Primary** | `midnight-9` | none | white | 12.75:1 | Default action |
| **Accent** | `accent-solid` `#cc4528` | none | white | 4.72:1 | The single most important action |
| **Outline** | transparent | 1.5px `orange-9` | `orange-11` | 5.13:1 | Secondary action |
| **Ghost** | transparent | 1px border | `slate-9` | — | Tertiary, toolbars |
| **Danger** | `#a8261c` | none | white | 7.10:1 | Destructive action |

{{% callout title="Accent buttons fill with accent-solid, not step 9" type="warning" %}}
`orange-9` (`#E05232`) is the identity accent and is unchanged as a mark,
border, active state, or syntax colour. But white on it measures **3.87:1** —
below the 4.5:1 floor for 13–14px labels. Solid accent controls therefore fill
with **`--color-accent-solid` (`#cc4528`)**, white at **4.72:1**; hover
uses opacity `0.88` rather than inventing a lighter or darker brand colour.
{{% /callout %}}

Hover is structural across components: primary surfaces move to `opacity:.88`
and card borders move to `midnight-7`. Pressed controls remain at their hover
state and never scale down. Do not lighten or darken a brand colour to create an
interaction state.

{{< rules >}}
{{% do %}}
Use exactly one accent button per view. Give every button a verb — "Deploy",
"Save changes".
{{% /do %}}
{{% dont %}}
Place two accent buttons side by side, or use a danger button for a reversible
action.
{{% /dont %}}
{{< /rules >}}

## Inputs

Default height 40px (sm 32, lg 48). Source Sans 3 at 13px, `slate-7` border,
and a 2px focus ring at midnight 15% alpha.

{{< demo label="Text, textarea, select" variant="grid" >}}
<div class="pj-field">
  <label class="pj-field__label" for="d1">Pipeline name</label>
  <input class="pj-input" id="d1" type="text" placeholder="validate-deploy">
</div>
<div class="pj-field">
  <label class="pj-field__label" for="d2">Policy</label>
  <select class="pj-input" id="d2"><option>strict</option><option>advisory</option></select>
</div>
<div class="pj-field">
  <label class="pj-field__label" for="d3">Description</label>
  <textarea class="pj-input" id="d3" placeholder="What does this pipeline do?"></textarea>
</div>
{{< /demo >}}

{{< demo label="States — default, focus, error, disabled" variant="grid" >}}
<div class="pj-field">
  <span class="pj-field__label">Default</span>
  <input class="pj-input" type="text" value="agents/auditor" readonly>
  <span class="pj-field__hint">slate-7 border</span>
</div>
<div class="pj-field">
  <span class="pj-field__label">Focus</span>
  <input class="pj-input" type="text" value="agents/auditor" readonly
         style="border-color:#1d3352;box-shadow:0 0 0 2px rgba(29,51,82,0.15)">
  <span class="pj-field__hint">midnight-9 + 2px ring</span>
</div>
<div class="pj-field">
  <span class="pj-field__label">Error</span>
  <input class="pj-input pj-input--error" type="text" value="agents/" readonly>
  <span class="pj-field__error">Agent identifier is incomplete.</span>
</div>
<div class="pj-field">
  <span class="pj-field__label">Disabled</span>
  <input class="pj-input" type="text" value="locked" disabled>
  <span class="pj-field__hint">slate-4 bg, cursor not-allowed</span>
</div>
{{< /demo >}}

{{< demo label="Checkbox, radio, toggle, slider" >}}
<label class="pj-check"><input type="checkbox" checked> Checked</label>
<label class="pj-check"><input type="checkbox"> Unchecked</label>
<label class="pj-check"><input type="radio" name="d" checked> Selected</label>
<label class="pj-check"><input type="radio" name="d"> Unselected</label>
<span class="pj-toggle pj-toggle--on"><span class="pj-toggle__track"><span class="pj-toggle__thumb"></span></span> On</span>
<span class="pj-toggle"><span class="pj-toggle__track"><span class="pj-toggle__thumb"></span></span> Off</span>
<input class="pj-range" type="range" value="60" style="max-width:160px">
{{< /demo >}}

Full rules — labels, validation, focus — are on
[Forms](/docs/interface/forms/).

## Cards

Radius 9px, padding 16px 24px, 1px `slate-5` border, flat at rest. `shadow-1`
is reserved for hover; nested
controls step **down** one radius.

Transparency is restricted. Use backdrop blur only for dark-on-dark modal
scrims and dark-panel inner cards, where the inner surface is
`rgba(255,255,255,.04)`. Do not put frosted glass on a light surface. Tags and
badges use solid pale tints, never alpha overlays.

{{< demo label="Basic, with image, with actions" variant="grid" >}}
<div class="pj-card">
  <div class="pj-card__body">
    <div class="pj-card__title">Validate &amp; deploy</div>
    <div class="pj-card__text">Runs policy checks before promoting to staging.</div>
  </div>
</div>
<div class="pj-card">
  <div class="pj-card__img">Image / diagram</div>
  <div class="pj-card__body">
    <div class="pj-card__title">Architecture</div>
    <div class="pj-card__text">Nodes, flows, and policy gates.</div>
  </div>
</div>
<div class="pj-card">
  <div class="pj-card__body">
    <div class="pj-card__title">Agent: auditor</div>
    <div class="pj-card__text">Idle for 2 hours.</div>
  </div>
  <div class="pj-card__actions">
    <button class="pj-btn pj-btn--primary pj-btn--sm">View details</button>
    <button class="pj-btn pj-btn--ghost pj-btn--sm">Download</button>
  </div>
</div>
{{< /demo >}}

## Tables

Header cells use the overline style. Striping uses the subtle background step,
never a border-only rule. **Wide tables scroll inside their own container.**

{{< demo label="Data table with sort indicator" variant="stack" >}}
<table class="pj-table">
  <thead><tr><th class="pj-table__sort">Pipeline</th><th>Policy</th><th>Agents</th><th>Status</th></tr></thead>
  <tbody>
    <tr><td>validate-deploy</td><td>strict</td><td>2</td><td><span class="pj-status pj-status--ok"><span class="pj-status__dot"></span>Healthy</span></td></tr>
    <tr><td>nightly-audit</td><td>advisory</td><td>1</td><td><span class="pj-status pj-status--warn"><span class="pj-status__dot"></span>Degraded</span></td></tr>
    <tr><td>release-train</td><td>strict</td><td>4</td><td><span class="pj-status pj-status--err"><span class="pj-status__dot"></span>Blocked</span></td></tr>
  </tbody>
</table>
{{< /demo >}}


### Table with search and filters

A toolbar above the table carries the search field and filter chips. Active
filters are shown as removable chips so the current view is always legible —
never leave a filter applied with no visible indication.

{{< demo label="Search, filter chips, pagination footer" variant="stack" >}}
<div class="pj-table-shell">
  <div class="pj-table-shell__toolbar">
    <input class="pj-table-shell__search" type="search" placeholder="Search pipelines…" aria-label="Search pipelines">
    <button class="pj-chip pj-chip--on">Policy: strict<span class="pj-chip__x">×</span></button>
    <button class="pj-chip pj-chip--on">Status: failing<span class="pj-chip__x">×</span></button>
    <button class="pj-chip">+ Add filter</button>
  </div>
  <table class="pj-table">
    <thead><tr><th class="pj-table__sort">Pipeline</th><th>Policy</th><th>Owner</th><th>Status</th></tr></thead>
    <tbody>
      <tr><td>release-train</td><td>strict</td><td>platform</td><td><span class="pj-status pj-status--err"><span class="pj-status__dot"></span>Blocked</span></td></tr>
      <tr><td>nightly-audit</td><td>strict</td><td>security</td><td><span class="pj-status pj-status--err"><span class="pj-status__dot"></span>Failed</span></td></tr>
    </tbody>
  </table>
  <div class="pj-table-shell__footer">
    <span>2 of 48 pipelines · 2 filters applied</span>
    <span class="pj-pagination">
      <button class="pj-pagination__page">‹</button>
      <button class="pj-pagination__page pj-pagination__page--current">1</button>
      <button class="pj-pagination__page">2</button>
      <button class="pj-pagination__page">›</button>
    </span>
  </div>
</div>
{{< /demo >}}

### Multi-level headers

Grouped columns use a two-row header. The group row is centred over its span and
separated by a vertical rule — the only place the system uses one.

{{< demo label="Two-row header with column groups" variant="stack" >}}
<div class="pj-scroll-x">
<table class="pj-table">
  <thead>
    <tr class="pj-table__group-head">
      <th rowspan="2">Pipeline</th>
      <th colspan="3">This week</th>
      <th colspan="3">Last week</th>
    </tr>
    <tr>
      <th class="pj-table__num">Runs</th><th class="pj-table__num">Pass</th><th class="pj-table__num">p95</th>
      <th class="pj-table__num">Runs</th><th class="pj-table__num">Pass</th><th class="pj-table__num">p95</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>validate-deploy</td><td class="pj-table__num">412</td><td class="pj-table__num">98.1%</td><td class="pj-table__num">4m12s</td><td class="pj-table__num">388</td><td class="pj-table__num">97.4%</td><td class="pj-table__num">4m40s</td></tr>
    <tr><td>nightly-audit</td><td class="pj-table__num">7</td><td class="pj-table__num">71.4%</td><td class="pj-table__num">18m03s</td><td class="pj-table__num">7</td><td class="pj-table__num">85.7%</td><td class="pj-table__num">17m22s</td></tr>
    <tr><td>release-train</td><td class="pj-table__num">14</td><td class="pj-table__num">92.9%</td><td class="pj-table__num">9m51s</td><td class="pj-table__num">12</td><td class="pj-table__num">91.7%</td><td class="pj-table__num">10m08s</td></tr>
  </tbody>
</table>
</div>
{{< /demo >}}

### Grouped rows and totals

Row groups act as sub-headings inside the table. A totals row is separated by a
2px rule — heavier than the body rules, so it reads as a summary rather than
another record.

{{< demo label="Row groups, indented children, totals" variant="stack" >}}
<table class="pj-table">
  <thead><tr><th>Workload</th><th class="pj-table__num">Runs</th><th class="pj-table__num">Cost</th></tr></thead>
  <tbody>
    <tr class="pj-table__rowgroup"><td colspan="3">Platform</td></tr>
    <tr><td class="pj-table__indent">validate-deploy</td><td class="pj-table__num">412</td><td class="pj-table__num">€48.10</td></tr>
    <tr><td class="pj-table__indent">release-train</td><td class="pj-table__num">14</td><td class="pj-table__num">€12.60</td></tr>
    <tr class="pj-table__rowgroup"><td colspan="3">Security</td></tr>
    <tr><td class="pj-table__indent">nightly-audit</td><td class="pj-table__num">7</td><td class="pj-table__num">€21.40</td></tr>
    <tr><td class="pj-table__indent">secret-scan</td><td class="pj-table__num">96</td><td class="pj-table__num">€9.05</td></tr>
    <tr class="pj-table__total"><td>Total</td><td class="pj-table__num">529</td><td class="pj-table__num">€91.15</td></tr>
  </tbody>
</table>
{{< /demo >}}

### Wide tables

A wide table scrolls **inside its own container**, never the page, and pins its
first column so the row identity stays visible while scrolling.

{{< demo label="Horizontal scroll with a sticky first column" variant="stack" >}}
<div class="pj-scroll-x">
<table class="pj-table pj-table--sticky" style="min-width:720px">
  <thead><tr><th>Pipeline</th><th>Policy</th><th>Owner</th><th class="pj-table__num">Runs</th><th class="pj-table__num">Pass</th><th class="pj-table__num">p50</th><th class="pj-table__num">p95</th><th>Region</th><th>Status</th></tr></thead>
  <tbody>
    <tr><td>validate-deploy</td><td>strict</td><td>platform</td><td class="pj-table__num">412</td><td class="pj-table__num">98.1%</td><td class="pj-table__num">2m01s</td><td class="pj-table__num">4m12s</td><td>eu-central</td><td><span class="pj-status pj-status--ok"><span class="pj-status__dot"></span>Healthy</span></td></tr>
    <tr><td>nightly-audit</td><td>strict</td><td>security</td><td class="pj-table__num">7</td><td class="pj-table__num">71.4%</td><td class="pj-table__num">11m40s</td><td class="pj-table__num">18m03s</td><td>eu-west</td><td><span class="pj-status pj-status--warn"><span class="pj-status__dot"></span>Degraded</span></td></tr>
  </tbody>
</table>
</div>
{{< /demo >}}

{{< rules >}}
{{% do %}}
Right-align numeric columns and set them in IBM Plex Mono so digits line up.
Show the active filter state and the result count together.
{{% /do %}}
{{% dont %}}
Add vertical rules between ordinary columns, let a wide table scroll the whole
page, or apply a filter without a visible chip saying so.
{{% /dont %}}
{{< /rules >}}

## Navigation

The navbar **follows the colour mode**: `midnight-1` at 88% alpha in light,
`midnight-dark-1` at 88% in dark, both over a 12px backdrop blur so content
scrolling underneath stays legible. It is separated by a **1px border**, not a
fill — the header is chrome, and a solid midnight band at the top of every page
spends the brand's darkest surface on navigation.

The active link carries a **2px `accent-solid` underline** plus the
high-emphasis text step. The underline is `accent-solid` rather than `orange-9`
because it sits directly against 13px text and is read as part of it.

{{< demo label="Navbar — follows the colour mode; 1px border, 2px accent underline on the active link" variant="stack" >}}
<div class="pj-navbar">
  <span class="pj-navbar__brand">projectious.work</span>
  <a class="pj-navbar__link pj-navbar__link--active" href="#">Documentation</a>
  <a class="pj-navbar__link" href="#">Pipelines</a>
  <a class="pj-navbar__link" href="#">Agents</a>
</div>
{{< /demo >}}

This specimen mirrors `.td-navbar` — the header at the top of this page. Switch
the theme and both change together.

{{< demo label="Breadcrumbs, tabs, pagination, sidebar" variant="grid" >}}
<div>
  <div class="pj-breadcrumb"><span><a href="#">Docs</a></span><span><a href="#">Interface</a></span><span>Components</span></div>
  <span class="pj-cap">13px, secondary</span>
</div>
<div>
  <div class="pj-tabs"><span class="pj-tabs__tab pj-tabs__tab--active">Overview</span><span class="pj-tabs__tab">Runs</span><span class="pj-tabs__tab">Settings</span></div>
  <span class="pj-cap">accent underline on active</span>
</div>
<div>
  <div class="pj-pagination"><button class="pj-pagination__page">‹</button><button class="pj-pagination__page pj-pagination__page--current">1</button><button class="pj-pagination__page">2</button><button class="pj-pagination__page">3</button><button class="pj-pagination__page">›</button></div>
  <span class="pj-cap">current in midnight-9</span>
</div>
<div>
  <div class="pj-sidebar">
    <div class="pj-sidebar__item pj-sidebar__item--active">Foundations</div>
    <div class="pj-sidebar__item">Logo</div>
    <div class="pj-sidebar__item">Interface</div>
  </div>
  <span class="pj-cap">active: 600 weight</span>
</div>
{{< /demo >}}

## Alerts and feedback

Four semantic colours as a 4px left border on a tinted background. The hues are
**mode-specific** — see [Colour](/docs/foundations/color/).

{{< demo label="Alerts" variant="stack" >}}
<div class="pj-alert pj-alert--info"><div class="pj-alert__title">Info</div><div class="pj-alert__text">Pipeline requires at least one validation agent.</div></div>
<div class="pj-alert pj-alert--success" style="margin-top:.5rem"><div class="pj-alert__title">Success</div><div class="pj-alert__text">All checks passed. Deployment ready.</div></div>
<div class="pj-alert pj-alert--warning" style="margin-top:.5rem"><div class="pj-alert__title">Warning</div><div class="pj-alert__text">Agent "monitor" has been idle for 2 hours.</div></div>
<div class="pj-alert pj-alert--danger" style="margin-top:.5rem"><div class="pj-alert__title">Danger</div><div class="pj-alert__text">Policy violation — deployment blocked.</div></div>
{{< /demo >}}

{{< demo label="Badges, progress, spinner" >}}
<span class="pj-badge">Default</span>
<span class="pj-badge pj-badge--accent">Urgent</span>
<span class="pj-badge pj-badge--pill">v0.1.0</span>
<span class="pj-badge pj-badge--accent pj-badge--pill">Breaking</span>
<span class="pj-progress"><span class="pj-progress__bar" style="width:62%"></span></span>
<span class="pj-spinner" role="status" aria-label="Loading"></span>
{{< /demo >}}

{{% callout title="Semantic colour is not decoration" type="info" %}}
Success, warning, and danger carry meaning. Do not use them to add visual
variety to neutral content.
{{% /callout %}}

## Modals and overlays

Modal radius 13px with `shadow-3`; scrim is midnight at 40% alpha. Focus is
trapped while open and restored to the trigger on close; `Esc` always closes.

{{< demo label="Dialog, dropdown, tooltip" variant="grid" >}}
<div class="pj-modal">
  <div class="pj-modal__head">Delete pipeline?</div>
  <div class="pj-modal__body">This removes the pipeline and its run history. This cannot be undone.</div>
  <div class="pj-modal__foot">
    <button class="pj-btn pj-btn--ghost pj-btn--sm">Cancel</button>
    <button class="pj-btn pj-btn--danger pj-btn--sm">Delete</button>
  </div>
</div>
<div class="pj-dropdown">
  <div class="pj-dropdown__item pj-dropdown__item--active">Re-run</div>
  <div class="pj-dropdown__item">Duplicate</div>
  <div class="pj-dropdown__item">Export logs</div>
</div>
<div><span class="pj-tooltip">Last run 4 minutes ago</span><span class="pj-cap">12px, 3px radius</span></div>
{{< /demo >}}

## Data display

{{< demo label="Avatar, stat card, tags" >}}
<span class="pj-avatar">JD</span>
<span class="pj-avatar pj-avatar--accent">AS</span>
<div class="pj-stat">
  <div class="pj-stat__value">1,284</div>
  <div class="pj-stat__label">Runs this week</div>
  <div class="pj-stat__delta">▲ 12% vs last week</div>
</div>
<span class="pj-badge">Composable</span>
<span class="pj-badge pj-badge--accent">Agent-first</span>
<span class="pj-badge">Humanistic</span>
{{< /demo >}}

{{< demo label="List and timeline" variant="grid" >}}
<div class="pj-list">
  <div class="pj-list__row"><span>validate-deploy</span><span class="pj-cap" style="margin:0">4m ago</span></div>
  <div class="pj-list__row"><span>nightly-audit</span><span class="pj-cap" style="margin:0">2h ago</span></div>
  <div class="pj-list__row"><span>release-train</span><span class="pj-cap" style="margin:0">1d ago</span></div>
</div>
<div class="pj-timeline">
  <div class="pj-timeline__item"><div>Policy check passed</div><span class="pj-timeline__time">09:14</span></div>
  <div class="pj-timeline__item pj-timeline__item--current"><div>Deploying to staging</div><span class="pj-timeline__time">09:16</span></div>
  <div class="pj-timeline__item"><div>Production</div><span class="pj-timeline__time">pending</span></div>
</div>
{{< /demo >}}

{{< demo label="Status indicators — always a dot plus a label" >}}
<span class="pj-status pj-status--ok"><span class="pj-status__dot"></span>Healthy</span>
<span class="pj-status pj-status--warn"><span class="pj-status__dot"></span>Degraded</span>
<span class="pj-status pj-status--err"><span class="pj-status__dot"></span>Failed</span>
<span class="pj-status pj-status--idle"><span class="pj-status__dot"></span>Idle</span>
{{< /demo >}}

{{< rules >}}
{{% do %}}
Pair every status dot with a text label, so meaning does not depend on colour
perception.
{{% /do %}}
{{% dont %}}
Use a bare coloured dot, or rely on red/green alone to distinguish states.
{{% /dont %}}
{{< /rules >}}

## Code and terminal

The code surface is dark by default. Use the complete light companion only for
an explicitly light panel; never switch it automatically with interface mode.
See [Code](/docs/interface/code/).

{{< demo label="Terminal" variant="stack" >}}
<div class="pj-terminal">
<span class="pj-terminal__prompt">$</span> tofu apply -auto-approve<br>
<span class="pj-terminal__out">Plan: 3 to add, 0 to change, 0 to destroy.</span><br>
<span class="pj-terminal__ok">✓</span> <span class="pj-terminal__out">Policy check passed</span><br>
<span class="pj-terminal__accent">●</span> <span class="pj-terminal__out">Deploying to staging…</span>
</div>
{{< /demo >}}


---
Source: https://projectious-work.github.io/brand/v3.0.0/docs/interface/components/index.md
