foundation / 01

Installation

tokens.css + button.css
head.html
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="components/button/button.css">

Basic

button / Basicinteractive

Code

button.html + button.css
button
<style>
.au-btn {
  --au-btn-bg: transparent;
  --au-btn-fg: var(--au-fg);
  --au-btn-border: transparent;
  --au-btn-radius: var(--au-radius-md);
  --au-btn-height: 36px;
  --au-btn-padding-inline: 12px;
  appearance: none;
  align-items: center;
  background: var(--au-btn-bg);
  border: 1px solid var(--au-btn-border);
  border-radius: var(--au-btn-radius);
  color: var(--au-btn-fg);
  cursor: pointer;
  display: inline-flex;
  font: 500 13px/1 var(--au-font-sans);
  gap: 8px;
  justify-content: center;
  letter-spacing: -0.01em;
  min-height: var(--au-btn-height);
  padding: 0 var(--au-btn-padding-inline);
  text-decoration: none;
  transition: background-color var(--au-duration-fast) var(--au-ease), border-color var(--au-duration-fast) var(--au-ease), box-shadow var(--au-duration-fast) var(--au-ease), color var(--au-duration-fast) var(--au-ease), transform 100ms ease-out;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.au-btn:hover:not(:disabled) {
  background: var(--au-btn-bg-hover, var(--au-btn-bg));
  border-color: var(--au-btn-border-hover, var(--au-btn-border));
  color: var(--au-btn-fg-hover, var(--au-btn-fg));}
.au-btn:focus-visible { box-shadow: var(--au-focus-ring); outline: 2px solid var(--au-focus); outline-offset: 2px; }
.au-btn:active:not(:disabled) { transform: scale(0.97); }
.au-btn:disabled,.au-btn[aria-disabled="true"] { cursor: not-allowed; opacity: 0.48; }
.au-btn--primary {
  --au-btn-bg: color-mix(in srgb, var(--au-action) 92%, transparent);
  --au-btn-bg-hover: var(--au-action);
  --au-btn-fg: var(--au-action-fg);
  --au-btn-border: transparent;
  --au-btn-radius: var(--au-radius-pill);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.au-btn--secondary {
  --au-btn-bg: var(--au-surface-muted);
  --au-btn-bg-hover: var(--au-surface-hover);
  --au-btn-border: transparent;
  --au-btn-radius: var(--au-radius-pill);
}
.au-btn--ghost {
  --au-btn-bg-hover: color-mix(in srgb, var(--au-fg) 7%, transparent);
  --au-btn-fg: var(--au-fg-muted);
  --au-btn-fg-hover: var(--au-fg-muted);
}
.au-btn--danger {
  --au-btn-bg: var(--au-danger);
  --au-btn-bg-hover: color-mix(in srgb, var(--au-danger) 86%, var(--au-fg));
  --au-btn-fg: var(--au-on-accent);
  --au-btn-border: var(--au-danger);
  --au-btn-radius: var(--au-radius-pill);
}
.au-btn--link {
  --au-btn-bg-hover: var(--au-action-soft);
  --au-btn-fg: var(--au-action);
  min-height: auto;
  padding-inline: 4px;
}
.au-btn--sm { --au-btn-height: 32px; --au-btn-padding-inline: 12px; font-size: 12px; }
.au-btn--lg { --au-btn-height: 44px; --au-btn-padding-inline: 16px; font-size: 14px; }
.au-btn--icon { --au-btn-padding-inline: 0; aspect-ratio: 1; width: var(--au-btn-height); }
.au-btn[data-au-state="loading"] { cursor: wait; pointer-events: none; }
.au-btn[data-au-state="loading"]::before {
  animation: au-button-spin 700ms linear infinite;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  border-right-color: transparent;
  content: "";
  height: 12px;
  width: 12px;
}
.au-btn[data-au-state="loading"] > * { opacity: 0.68; }
@keyframes au-button-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.au-btn,.au-btn[data-au-state="loading"]::before { animation: none; transition-duration: 0.01ms; }
}
@media (max-width: 640px) {
.au-btn:not(.au-btn--link) { --au-btn-height: 40px; min-width: 40px; }
}
@media (pointer: coarse) {
.au-btn { --au-btn-height: 44px; min-width: 44px; }
}
</style>

<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center"><button class="au-btn au-btn--primary" type="button">Run component</button><button class="au-btn au-btn--secondary" type="button">Secondary</button><button class="au-btn au-btn--ghost" type="button">Ghost</button><button class="au-btn au-btn--primary" type="button" data-au-state="loading" aria-busy="true">Working</button></div>

Component contract

Install
tokens.css · button.css
States
idle, loading (aria-busy), and disabled via native disabled attribute
Host
click handling, disabled timing, and any async loading state
For agents
button.contract
# agent-ui / button
component: button
selector: .au-button
data_au_component: button
purpose: Primary, secondary, and ghost actions with loading and size variants.

install:
  - tokens.css
  - button.css

states: idle, loading (aria-busy), and disabled via native disabled attribute.
css_variables: --au-btn-height, --au-btn-padding-inline
events: native form, focus, and click only — no custom events or runtime required
no_javascript: Native buttons remain usable without script.
host_owns: The host owns click handling, disabled timing, and any async loading state.
accessibility: Use a clear label. Loading buttons keep their label and expose busy state.
security: Buttons never execute tools. Host owns click handlers and authorization.

rules:
  - Host owns execution, authorization, persistence, validation, and state transitions.
  - Components render host-provided state; they never execute tools or imply approval.
  - Escape untrusted model output, tool args, and tool output before insertion.
  - Use only documented data-au-* attributes and data-au-state values.
  - Keep the no-JS path usable. Do not add React, Tailwind, bundlers, or remote assets.
source: components/button/button.{html,css} + README.md + fixture.html