Built once, rendered everywhere
Every component ships as a typed React export and the plain CSS class it renders — so a Hugo partial and a React page produce pixel-identical UI. Demos below are live and theme-aware; flip the theme toggle to check dark mode.
Buttons
Variants & sizes
One primary action per view. ghost for tertiary actions, semantic variants for their outcomes.
import { Button } from '@readysetcloud/ui';
<Button variant="primary">Primary</Button>
<Button variant="ghost" size="sm">Ghost</Button>
<Button loading loadingLabel="Saving…">Save</Button>
<button class="btn btn-primary">Primary</button>
<button class="btn btn-ghost btn-sm">Ghost</button>
<button class="btn btn-primary" disabled>
<span class="spinner"></span> Saving…
</button>
Badges & status
Badge
The uppercase mono tag — for categorical labels like tiers, environments, and content types.
import { Badge } from '@readysetcloud/ui';
<Badge variant="success">Success</Badge>
<span class="badge badge-success">Success</span>
StatusBadge
The sentence-case health pill — for state readouts and threshold flags. One tone class is correct in both themes; the ramps invert for you.
import { StatusBadge } from '@readysetcloud/ui';
<StatusBadge tone="success" icon="✓">Stable</StatusBadge>
<StatusBadge tone="warning" icon="⚠">High</StatusBadge>
<span class="status-badge status-badge-success" role="status">
<span aria-hidden="true">✓</span> Stable
</span>
Analytics kit
The shared language of the dashboards: metric tiles with delta pills, threshold badges, and trend sparklines, plus the segmented control that switches their baseline.
TrendPill
Color encodes improvement, not direction — pass invert for metrics where down is good (bounce, unsubscribes). Zero deltas render neutral with no arrow.
import { TrendPill } from '@readysetcloud/ui';
<TrendPill delta={6.3} /> // improvement → success colors
<TrendPill delta={-2.1} /> // decline → error colors
<TrendPill delta={-0.4} invert /> // bounce rate fell → improvement
<TrendPill delta={0} /> // neutral, no arrow
<span class="trend-pill trend-pill-positive">
<svg viewBox="0 0 16 16" aria-hidden="true">
<path d="M3 10.5 8 5.5 13 10.5" fill="none"
stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
+6.3%
</span>
SegmentedControl
Compact multi-option toggle for comparison baselines, view switches, and filter tabs. State is exposed via aria-pressed. Try it — this one is live.
import { SegmentedControl } from '@readysetcloud/ui';
<SegmentedControl
aria-label="Comparison baseline"
options={[
{ value: 'average', label: 'Average' },
{ value: 'last', label: 'Last issue' },
{ value: 'best', label: 'Best issue' }
]}
value={mode}
onChange={setMode}
/>
<div class="segmented-control" role="group" aria-label="Comparison baseline">
<button class="segmented-control-option" aria-pressed="true">Average</button>
<button class="segmented-control-option" aria-pressed="false">Last issue</button>
<button class="segmented-control-option" aria-pressed="false">Best issue</button>
</div>
StatTile + TrendSparkline
The dashboard metric tile: uppercase label, display-font value, delta pill, threshold badge, caption, and a decorative sparkline with the latest point emphasized. Sparklines are aria-hidden — the numbers carry the meaning.
import { StatTile } from '@readysetcloud/ui';
<StatTile
label="Open Rate"
value="48.0%"
delta={6.3}
meta="1,234 opens · vs. avg"
sparkline={[38, 42, 40, 45, 43, 48]}
/>
<StatTile
label="Bounce Rate"
value="6.2%"
delta={-0.4}
invertDelta // falling bounce = improvement
status={{ tone: 'warning', label: 'High' }}
meta="67 bounced · vs. avg"
sparkline={[3.1, 3.4, 4.2, 4.0, 5.1, 6.2]}
/>
<div class="stat-tile">
<div class="stat-tile-header">
<div>
<div class="stat-tile-label">Open Rate</div>
<div class="stat-tile-row">
<span class="stat-tile-value">48.0%</span>
<span class="trend-pill trend-pill-positive">+6.3%</span>
</div>
</div>
</div>
<div class="stat-tile-meta">1,234 opens · vs. avg</div>
<div class="stat-tile-footer"><div id="opens-trend"></div></div>
</div>
<script>
// window.rscUi from ui.global.js
rscUi.renderSparkline(
document.getElementById('opens-trend'),
[38, 42, 40, 45, 43, 48]
);
</script>
Page hero
PageHero
The gradient band that opens a page: surface-to-primary wash, blurred accent blob (drawn in CSS — no extra markup), display-font title, and pill meta chips.
Serverless Picks of the Week #204
Sent to your loyal readers, tracked end to end.
import {
PageHero, PageHeroTitle, PageHeroSubtitle, PageHeroChips, PageHeroChip
} from '@readysetcloud/ui';
<PageHero>
<PageHeroTitle>Serverless Picks of the Week #204</PageHeroTitle>
<PageHeroSubtitle>Sent to your loyal readers, tracked end to end.</PageHeroSubtitle>
<PageHeroChips>
<PageHeroChip>Issue #204</PageHeroChip>
<PageHeroChip tone="success">Sent Jul 21</PageHeroChip>
</PageHeroChips>
</PageHero>
<header class="page-hero">
<div class="page-hero-content">
<h1 class="page-hero-title">Serverless Picks of the Week #204</h1>
<p class="page-hero-subtitle">Sent to your loyal readers.</p>
<div class="page-hero-chips">
<span class="page-hero-chip">Issue #204</span>
<span class="page-hero-chip page-hero-chip-success">Sent Jul 21</span>
</div>
</div>
</header>
Cards
Card
The compound container behind most content: header, title, description, body, footer.
Sending health
Deliverability signals for the last 10 issues.
import { Card, CardHeader, CardTitle, CardDescription, CardBody, CardFooter } from '@readysetcloud/ui';
<Card>
<CardHeader>
<CardTitle>Sending health</CardTitle>
<CardDescription>Deliverability signals.</CardDescription>
</CardHeader>
<CardBody>…</CardBody>
<CardFooter>…</CardFooter>
</Card>
<div class="card">
<div class="card-header">
<h3 class="card-title">Sending health</h3>
<p class="card-description">Deliverability signals.</p>
</div>
<div class="card-body">…</div>
<div class="card-footer">…</div>
</div>
Forms
Fields
Label/aria wiring is automatic in React. Inputs hold ≥16px text on mobile (no iOS zoom) and show the azure focus ring.
import { Input, Select, CodeInput } from '@readysetcloud/ui';
<Input label="Email address" type="email" hint="We'll never share it." />
<Input label="Newsletter name" error="A name is required." />
<Select label="Tier"><option>Free</option></Select>
<CodeInput label="Verification code" />
<div class="field">
<label class="field-label" for="email">Email address</label>
<input class="input" id="email" type="email">
<span class="field-hint">We'll never share it.</span>
</div>
<input class="input input-error">
<span class="field-error" role="alert">A name is required.</span>
Alerts, toasts & modals
Alert
Inline outcome messaging. Error alerts get role="alert".
import { Alert } from '@readysetcloud/ui';
<Alert variant="success">Issue #204 scheduled.</Alert>
<Alert variant="error">We couldn't reach SendGrid.</Alert>
<div class="alert alert-success">Issue #204 scheduled.</div>
<div class="alert alert-error" role="alert">We couldn't reach SendGrid.</div>
Toast
Transient confirmations, bottom-right (full-width on phones). Mount ToastProvider once; errors linger 8s, others 5s.
import { ToastProvider, useToast } from '@readysetcloud/ui';
const { toast } = useToast();
toast('Subscriber imported.', { variant: 'success' });
<div class="toast-viewport">
<div class="toast toast-success">Subscriber imported.</div>
</div>
Modal
Native <dialog> — Esc/backdrop close and focus trapping for free. Becomes a bottom sheet on small screens.
import { Modal } from '@readysetcloud/ui';
<Modal open={open} onClose={() => setOpen(false)} aria-label="Delete issue">
…
</Modal>
<dialog class="modal" id="confirm" aria-label="Delete issue">…</dialog>
<script>document.getElementById('confirm').showModal();</script>
Loading & empty states
Spinner, skeleton, progress
Use the composed loading states from the package instead of app-local loaders.
import { InlineLoading, Skeleton, ProgressIndicator } from '@readysetcloud/ui';
<InlineLoading isLoading loadingText="Sending…" />
<Skeleton width="75%" />
<ProgressIndicator steps={[
{ id: 'domain', label: 'Domain added', status: 'completed' },
{ id: 'dns', label: 'DNS propagating', status: 'in-progress' },
{ id: 'test', label: 'Send test email', status: 'pending' }
]} />
<span class="spinner"></span>
<div class="skeleton" style="width: 75%; height: 1rem;"></div>
<div class="progress-step" data-status="completed">…</div>
EmptyState
import { EmptyState, Button } from '@readysetcloud/ui';
<EmptyState
title="No issues yet"
description="Write your first issue and it will show up here."
icon="📭"
action={<Button size="sm">New issue</Button>}
/>
<div class="empty-state">
<span class="empty-state-title">No issues yet</span>
<span>Write your first issue and it will show up here.</span>
</div>