app-page · page-header · section-header · toolbar · cardProjects
Durable software owned by this account.
Runtime overview
3Applications currently reachable through Campus Compute.
Campus UI is the semantic interface system for Campus-native applications. The gallery below renders the current components in context; use the patterns directly from plain HTML, JavaScript, React, or any framework that can emit accessible HTML.
These are the controls and compositions developers ship—not a list of component names.
app-page · page-header · section-header · toolbar · cardDurable software owned by this account.
Applications currently reachable through Campus Compute.
button · icon-button · action-menu · action-popovercollection · row-copy · avatar · metric · factnotice · banner · status-pill · badge · steps · loading · emptyfield · choice-field · segmented-control · form-actionsconversation · message · composer · microphone-icon · send-iconcommand-input · command-group · command-rowconnection-status · protected-state · assurance-list · sheet · confirmation-sheetContinue with a user gesture before reading browser-protected data.
This will use Campus Compute for this installation.
Every Campus interface loads the content-addressed stylesheet published by the Campus UI manifest. During local authoring, use the stable alias; the package build rewrites it to the immutable URL.
<link rel="stylesheet" href="/assets/campus-app.css">
<main class="campus-page" data-campus-tint="intelligence">
<!-- Campus UI components -->
</main>const stylesheet = document.createElement('link')
stylesheet.rel = 'stylesheet'
stylesheet.href = '/assets/campus-app.css'
document.head.append(stylesheet)
document.documentElement.dataset.campusTint = 'intelligence'export function CampusApp({ children }) {
return (
<main className="campus-page" data-campus-tint="intelligence">
{children}
</main>
)
}Use semantic elements and Campus class names. The stylesheet owns touch sizing, tones, focus treatment, appearance, and responsive behavior.
<button class="campus-button campus-button-primary">
<span class="campus-button-label">Deploy</span>
</button>
<span class="campus-status-pill campus-status-success">Ready</span>function campusButton(label, tone = 'primary') {
const button = document.createElement('button')
button.type = 'button'
button.className = `campus-button campus-button-${tone}`
button.innerHTML = '<span class="campus-button-label"></span>'
button.firstElementChild.textContent = label
return button
}
actions.append(campusButton('Deploy'), campusButton('Cancel', 'secondary'))function Button({ tone = 'primary', busy = false, children, ...props }) {
return (
<button
type="button"
className={`campus-button campus-button-${tone}`}
aria-busy={busy || undefined}
disabled={busy || props.disabled}
{...props}
>
<span className="campus-button-label">{busy ? 'Working…' : children}</span>
</button>
)
}
<Button tone="tint">New project</Button>Rows stay keyboard-accessible because the action is a native link or button. Compose identity, copy, metadata, and state without rebuilding row spacing in every app.
<section class="campus-collection" aria-label="Projects">
<button class="campus-collection-row" aria-label="Open Research agent">
<span class="campus-avatar" aria-hidden="true">AI</span>
<span class="campus-row-copy">
<span class="campus-row-eyebrow">INTELLIGENCE</span>
<strong class="campus-row-title">Research agent</strong>
<span class="campus-row-detail">Persistent tools and model access</span>
<span class="campus-row-meta">Updated 2m ago</span>
</span>
<span class="campus-status-pill campus-status-success">Ready</span>
</button>
</section>function projectRow(project) {
const row = document.createElement('button')
row.type = 'button'
row.className = 'campus-collection-row'
row.setAttribute('aria-label', `Open ${project.name}`)
row.innerHTML = `
<span class="campus-avatar" aria-hidden="true"></span>
<span class="campus-row-copy">
<strong class="campus-row-title"></strong>
<span class="campus-row-detail"></span>
</span>`
row.querySelector('.campus-avatar').textContent = project.initials
row.querySelector('.campus-row-title').textContent = project.name
row.querySelector('.campus-row-detail').textContent = project.detail
return row
}function ProjectRow({ project, onOpen }) {
return (
<button
type="button"
className="campus-collection-row"
aria-label={`Open ${project.name}`}
onClick={() => onOpen(project.id)}
>
<span className="campus-avatar" aria-hidden="true">{project.initials}</span>
<span className="campus-row-copy">
<strong className="campus-row-title">{project.name}</strong>
<span className="campus-row-detail">{project.detail}</span>
</span>
<span className="campus-status-pill campus-status-success">Ready</span>
</button>
)
}The same semantic markup works for settings, compose flows, and agent interfaces.
<label class="campus-field">
Application name
<input name="name" autocomplete="off">
<span class="campus-field-help">Shown in Launcher and Store.</span>
</label>
<form class="campus-composer" aria-label="Message composer">
<div class="campus-composer-main">
<input name="message" aria-label="Message" placeholder="Ask the agent…">
<button class="campus-icon-button" aria-label="Send message">↑</button>
</div>
</form>composer.addEventListener('submit', async (event) => {
event.preventDefault()
const data = new FormData(composer)
await campus.backend.json('/messages', {
method: 'POST',
body: { text: data.get('message') },
})
})function Composer({ onSend }) {
const [message, setMessage] = useState('')
function submit(event) {
event.preventDefault()
if (!message.trim()) return
onSend(message.trim())
setMessage('')
}
return (
<form className="campus-composer" aria-label="Message composer" onSubmit={submit}>
<div className="campus-composer-main">
<input value={message} onChange={(event) => setMessage(event.target.value)} />
<button className="campus-icon-button" aria-label="Send message">↑</button>
</div>
</form>
)
}Use a Campus sheet for explicit confirmation, protected state, and workload-start boundaries. Avoid native confirm() and prompt().
<campus-modal-layer class="campus-sheet-layer" role="presentation">
<section class="campus-sheet" role="dialog" aria-modal="true" aria-labelledby="confirm-title">
<header class="campus-sheet-header">
<div>
<span class="campus-eyebrow">CONFIRM ACTION</span>
<h2 id="confirm-title">Start private workload?</h2>
<p>This will use Campus Compute for this installation.</p>
</div>
<button class="campus-icon-button" aria-label="Close">×</button>
</header>
<div class="campus-form-actions campus-form-actions-end">
<button class="campus-button campus-button-secondary">Cancel</button>
<button class="campus-button campus-button-primary">Start workload</button>
</div>
</section>
</campus-modal-layer>const dialog = document.querySelector('[role="dialog"]')
const close = () => dialog.closest('campus-modal-layer').remove()
dialog.querySelector('[aria-label="Close"]').addEventListener('click', close)
dialog.addEventListener('keydown', (event) => {
if (event.key === 'Escape') close()
})
dialog.querySelector('button').focus()function ConfirmationSheet({ title, description, onCancel, onConfirm }) {
return (
<div className="campus-sheet-layer" role="presentation" onMouseDown={onCancel}>
<section
className="campus-sheet"
role="dialog"
aria-modal="true"
aria-labelledby="confirm-title"
onMouseDown={(event) => event.stopPropagation()}
>
<header className="campus-sheet-header">
<div><h2 id="confirm-title">{title}</h2><p>{description}</p></div>
<button className="campus-icon-button" aria-label="Close" onClick={onCancel}>×</button>
</header>
<div className="campus-form-actions campus-form-actions-end">
<button className="campus-button campus-button-secondary" onClick={onCancel}>Cancel</button>
<button className="campus-button campus-button-primary" onClick={onConfirm}>Confirm</button>
</div>
</section>
</div>
)
}The gallery covers the visible output of the current library. The full exported surface is grouped below for quick reference.
| Area | Components |
|---|---|
| Structure | group, app-page, page-header, section-header, toolbar, card |
| Collections and data | collection, collection-row, row-copy, avatar, metric-grid, metric, fact-list, fact |
| Actions and menus | button, icon-button, panel-close-button, form-actions, action-menu, action-menu-item, action-popover, action-popover-item |
| Sheets | sheet, sheet-header, confirmation-sheet, workload-action-sheet |
| State | assurance-list, connection-status, protected-state, steps, loading-state, notice, banner, status-pill, badge, state-dot, empty-state, state-view |
| Conversation and commands | conversation, message, composer, search-field, command-surface, command-input, command-group, command-row, segmented-control |
| Forms and icons | field, choice-field, microphone-icon, stop-icon, send-icon, panel-close-icon |
Campus UI emits accessible, semantic HTML and uses a shared content-addressed stylesheet. The stylesheet owns shell/content insets, safe areas, touch targets, light/dark appearance, mobile widths, desktop layouts, focus treatment, and common interaction states. App-local CSS should describe only composition and identity unique to that application.
Semantic tints are graphite for Campus/default, green for communication, orange for finance, blue for intelligence, cyan for files, and purple for OpenClaw-specific experiences.
For the source of truth, see frontend/src/campus/ui.cljs and the Campus UI design contract.