Dashboard Design skill for Codex
Answer the top questions in ten seconds. Codex starts a dashboard from the user's ranked questions rather than the available charts. It picks the dashboard type, puts three to five KPI cards with explicit comparisons at the top, colors deltas by whether up is good, keeps filters in the URL, formats numbers with Intl.NumberFormat, and gives every widget its own loading, empty, partial and error state.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- dashboard-design Installs to ~/.agents/skills/dashboard-design/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install dashboard-design The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project dashboard-design Installs to .agents/skills/dashboard-design/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/dashboard-design.zipunzip -o dashboard-design.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) dashboard-design PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $dashboard-design, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs dashboards that answer specific questions - operational, analytical and executive types, question-first layout, KPI hierarchy and KPI card anatomy with comparison deltas, direction-aware color, density and grid, global versus local filters, number formatting with Intl.NumberFormat, tables, and loading, empty, partial and stale-data states. Use when building or redesigning an admin, analytics, monitoring or reporting dashboard, when a dashboard is cluttered or nobody uses it, or when deciding which metrics go at the top.
Use it when
- Building or redesigning an admin, analytics, monitoring or reporting dashboard
- Deciding which metrics belong at the top and how to show change
- A dashboard is cluttered, slow to read or unused
- Adding filters, time ranges and data-freshness indicators
Use something else when
- Choosing and styling individual chart types: use data-visualization-design
- Implementing charts with D3 code: use d3-visualizations
How to check the result
From a single screenshot, each ranked question can be answered in about ten seconds, and every number shows a unit, a period and a comparison.
The mistake it prevents
Coloring every upward delta green, so a rise in churn or error rate looks like good news.
A dashboard is good when its intended user can answer their top questions in under ten seconds without hovering or clicking, the most important number is the most prominent thing on screen, every number has context (compared to what, over what period), and the data’s freshness and gaps are visible. A wall of equally weighted charts is a data dump.
Before designing
- Identify the user and the moment: on-call engineer at 3 a.m., sales lead on Monday,
CFO monthly. Read any PRD, issue or
AGENTS.mdnotes; inspect the data model and API. - Write the questions, ranked. Three to five; each becomes a region of the screen:
1. Are we on track for the month's revenue target? (daily)
2. Which channel is driving the change? (when 1 is off)
3. Which accounts are at risk this week? (act on it)
- Classify the dashboard; it determines density and refresh:
| Type | User | Shape | Refresh |
|---|---|---|---|
| Operational / monitoring | On-call, support, ops | Status first, alerts, real-time trends, few filters | Seconds to minutes |
| Analytical | Analysts, PMs | Filters, drill-down, comparisons, tables, export | On demand |
| Strategic / executive | Leadership | 4–6 KPIs against targets, monthly trends, annotations | Daily or slower |
Do not combine all three on one screen; link between them.
Layout hierarchy
Top to bottom, left to right in reading order of importance:
- Header: title, time range, global filters, last-updated timestamp.
- KPI row: 3–5 headline numbers that answer question 1. Not 12.
- Primary trend: the chart that explains the main KPI over time, full width or 8/12.
- Breakdown: what drives it (by segment, channel, region), 2–3 charts.
- Detail: a table of the items to act on, sortable, linked to records.
Use a 12-column grid with consistent gutters (16–24 px); widgets span 3, 4, 6, 8 or 12 columns. Align widget tops and heights within a row. Give the KPI row breathing room; dense is fine below it.
KPI card anatomy
Revenue (MTD) [i]
$1.24M ▲ 8.2% vs last month
[sparkline: last 30 days] Target $1.40M · 89%
- Label states metric and period. Value is the largest text on the card (28–40 px), tabular numerals.
- Delta always says against what (
vs last month,vs target). A bare+8%is ambiguous. - Color the delta by meaning, not direction: revenue up is good, churn up is bad. Pair color with an arrow and sign so it survives color blindness and grayscale.
- Sparklines carry shape, not values; no axes, consistent time span across cards.
type Kpi = { label: string; value: number; previous: number; format: "currency" | "percent" | "number"; higherIsBetter: boolean };
const fmt = (v: number, f: Kpi["format"]) =>
new Intl.NumberFormat(undefined, {
style: f === "currency" ? "currency" : f === "percent" ? "percent" : "decimal",
currency: "USD",
notation: Math.abs(v) >= 10_000 && f !== "percent" ? "compact" : "standard",
maximumFractionDigits: f === "percent" ? 1 : 2,
}).format(v);
export function KpiCard({ label, value, previous, format, higherIsBetter }: Kpi) {
const change = previous === 0 ? null : (value - previous) / Math.abs(previous);
const good = change === null ? null : (change >= 0) === higherIsBetter;
return (
<section className="kpi" aria-label={label}>
<h3 className="kpi-label">{label}</h3>
<p className="kpi-value">{fmt(value, format)}</p>
{change === null ? (
<p className="kpi-delta">No prior data</p>
) : (
<p className={`kpi-delta ${good ? "is-good" : "is-bad"}`}>
<span aria-hidden="true">{change >= 0 ? "▲" : "▼"}</span>{" "}
{new Intl.NumberFormat(undefined, { style: "percent", maximumFractionDigits: 1, signDisplay: "always" }).format(change)}{" "}
vs previous period
</p>
)}
</section>
);
}
Charts and tables
- Line for trends over time, bar for comparing categories, stacked bar only when the
total matters more than the parts; avoid pies beyond 3 slices and gauges entirely
(a number against a target is clearer). Full guidance:
data-visualization-design. - Use the same color for the same series across every widget; reserve the alert color.
- Annotate events on trend charts (deploys, campaigns, outages) instead of explaining them in a meeting.
- Tables: right-align numbers, left-align text, tabular numerals, sticky header,
consistent units in the header (
Revenue (USD)) instead of per cell, row hover, and a clear sort indicator. Show 10–25 rows with pagination or virtualization beyond that.
Filters and time
- Global filters (time range, segment) live in the header and affect every widget. Local filters belong to one widget and appear inside it. Never mix silently.
- Persist filters in the URL query string so views are shareable and survive reload.
- Time range presets: today, 7d, 30d, quarter to date, custom. Show the comparison period explicitly.
- Show the timezone when data is time-bucketed by day.
States
| State | Design |
|---|---|
| Loading | Skeleton per widget at final size; never one spinner for the page |
| Empty | Explain why (no data yet, filter too narrow) and offer the fix |
| Partial | Mark incomplete periods (today, current week) with a dashed segment or label |
| Stale | “Updated 14 min ago” in the header; warn visibly past the expected refresh |
| Error | Widget-level error with retry; the rest of the dashboard keeps working |
| Zero vs null | Render 0 for zero, — with a tooltip for no data; never blank |
Density and responsiveness
- Desktop first for analytical dashboards; on mobile, stack by priority: KPI row (2 per row), primary trend, action table as a list. Hide secondary breakdowns behind a tab rather than shrinking them unreadably.
- Compact density for analysts (row height 32–36 px), comfortable for executives (44–52 px). Offer a toggle if both use it.
Workflow
- [ ] 1. User, moment, type; ranked questions
- [ ] 2. Map each question to a region and a visual (table below)
- [ ] 3. Wireframe the grid; KPI row first
- [ ] 4. Define formatting rules (currency, compact, percent, dates, timezone)
- [ ] 5. Build widgets with all states; filters in URL
- [ ] 6. Verify with real data volumes and edge values
| Question | Region | Visual | Data source |
|---|---|---|---|
| On track for target? | KPI row | KPI cards vs target | /metrics/revenue?range=mtd |
Verification
- Ten-second test: from a screenshot alone, answer each ranked question. Any you cannot answer is a design bug.
- Load real or production-like data, including extremes: zero, negative, 9-digit values, long names, empty segments.
- Check that every number has a unit and a comparison and that colors match meaning.
- Throttle the network; widgets load independently and errors stay contained.
- Keyboard through filters and tables; charts have a text or table alternative.
Gotchas
- Vanity KPIs (total signups ever) at the top instead of actionable ones.
- Percent change from a tiny base (+400% from 1 to 5) shown without the absolute values.
- Averages hiding distribution; show p50 and p95 for latency-like metrics.
- Dual-axis charts that imply correlation; use two aligned charts.
- Auto-refresh that resets scroll, filters or open tooltips.
Output contract
**User / moment / type:** <...>
**Questions (ranked):** 1. ... 2. ... 3. ...
**Layout:** header (filters, updated time) / KPI row (n) / primary trend / breakdowns / action table
**Formatting:** currency USD compact >= 10k, percent 1 dp, dates <format>, timezone <tz>
**States covered:** loading, empty, partial, stale, error, zero vs null
**Checks:** ten-second test passed for questions 1-3; tested with <data set> What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Dashboard Design"
short_description: "Answer the top questions in ten seconds"
default_prompt: "$dashboard-design Design the analytics dashboard for our SaaS admin, nobody looks at the current one" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.