dashboard-design

Dashboard architecture and UX: KPI hierarchy, information density decisions, filter patterns, drill-down navigation, real-time update strategies (polling vs. WebSocket vs. SSE), empty and loading states for charts, and responsive dashboard layouts. Use when designing or building any analytics dashboard.

8 stars

Best use case

dashboard-design is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Dashboard architecture and UX: KPI hierarchy, information density decisions, filter patterns, drill-down navigation, real-time update strategies (polling vs. WebSocket vs. SSE), empty and loading states for charts, and responsive dashboard layouts. Use when designing or building any analytics dashboard.

Teams using dashboard-design should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/dashboard-design/SKILL.md --create-dirs "https://raw.githubusercontent.com/marvinrichter/clarc/main/skills/dashboard-design/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/dashboard-design/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How dashboard-design Compares

Feature / Agentdashboard-designStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Dashboard architecture and UX: KPI hierarchy, information density decisions, filter patterns, drill-down navigation, real-time update strategies (polling vs. WebSocket vs. SSE), empty and loading states for charts, and responsive dashboard layouts. Use when designing or building any analytics dashboard.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Dashboard Design Skill

## When to Activate

- Designing or building an analytics dashboard from scratch
- Deciding on layout, widget placement, or information hierarchy
- Adding filters, date pickers, or drill-down navigation
- Implementing real-time data updates
- Designing empty, loading, and error states for dashboard widgets
- Adapting a single dashboard layout to serve both executive and analyst audiences
- Serializing filter state to URL parameters so dashboards can be bookmarked and shared

---

## KPI Hierarchy

Structure information by importance, not by data availability.

### Layout tiers

```
┌─────────────────────────────────────────────────────────────────┐
│  PRIMARY KPIs  (large number, trend indicator, sparkline)        │
│  Revenue: $1.2M  ↑12%   Users: 48,320  ↑3%   NPS: 67  →0%    │
├─────────────────────────────────────────────────────────────────┤
│  SECONDARY CHARTS  (medium size, 2-3 per row)                    │
│  ┌───────────┐  ┌───────────┐  ┌───────────┐                   │
│  │ Sales     │  │ Traffic   │  │ Retention │                   │
│  │ by region │  │ sources   │  │ cohort    │                   │
│  └───────────┘  └───────────┘  └───────────┘                   │
├─────────────────────────────────────────────────────────────────┤
│  SUPPORTING TABLES / DETAIL VIEWS  (full width, below the fold) │
│  Recent transactions | Top pages | Conversion funnel           │
└─────────────────────────────────────────────────────────────────┘
```

### Reading pattern

- **F-Pattern** — for data-dense dashboards (analysts); top-left priority
- **Z-Pattern** — for executive dashboards; headline metric top-left, key visual top-right, CTA bottom-right

### Audience-specific density

| Audience | Density | Interaction |
|----------|---------|-------------|
| Executive | Low — 3-5 KPIs, one chart | No filters, no drill-down |
| Manager | Medium — 6-10 KPIs, 3-4 charts | Date range, department filter |
| Analyst | High — full data tables, custom filters | Drill-down, export, comparisons |

---

## Information Density

### Progressive disclosure

Show summary → reveal detail on interaction:

```
Summary card: "Revenue: $1.2M ↑12%"
  → Click → Expand: monthly breakdown chart
  → Click "View details" → Full page with table + filters
```

### Drill-down pattern

```typescript
interface DrillDownState {
  level: 'overview' | 'region' | 'store';
  selection: string | null;
}

function Dashboard() {
  const [drill, setDrill] = useState<DrillDownState>({ level: 'overview', selection: null });

  return (
    <>
      <Breadcrumb drill={drill} onNavigate={setDrill} />
      {drill.level === 'overview' && <OverviewChart onDrillDown={(region) => setDrill({ level: 'region', selection: region })} />}
      {drill.level === 'region' && <RegionChart region={drill.selection!} onDrillDown={(store) => setDrill({ level: 'store', selection: store })} />}
      {drill.level === 'store' && <StoreDetail store={drill.selection!} />}
    </>
  );
}
```

---

## Filter Design

### Global vs. local filters

- **Global filters** (date range, user segment) — apply to all widgets; place in page header
- **Local filters** (sort order, top N) — apply to one widget; place inside the widget card

### Filter state URL serialization

```typescript
// Serialize filters to URL params — enables sharing and browser back/forward
import { useSearchParams } from 'react-router-dom';

function useFilters() {
  const [searchParams, setSearchParams] = useSearchParams();

  const filters = {
    dateRange: searchParams.get('dateRange') ?? '30d',
    region: searchParams.get('region') ?? 'all',
  };

  const setFilter = (key: string, value: string) => {
    const next = new URLSearchParams(searchParams);
    next.set(key, value);
    setSearchParams(next, { replace: true });
  };

  return { filters, setFilter };
}
```

### Filter reset

Always provide a visible "Reset filters" link when non-default filters are active:

```tsx
{hasActiveFilters && (
  <button onClick={resetFilters} className="text-sm text-blue-600 hover:underline">
    Reset filters
  </button>
)}
```

### "No results" state

When filters produce no data, explain why and offer an escape:

```tsx
function NoResults({ filters }: { filters: Filters }) {
  return (
    <div className="flex flex-col items-center py-16">
      <SearchOffIcon className="h-12 w-12 text-gray-400" />
      <p className="mt-2 font-medium">No data for these filters</p>
      <p className="text-sm text-gray-500">
        Try changing the date range or removing the region filter.
      </p>
      <button onClick={resetFilters} className="mt-4 btn-secondary">
        Clear filters
      </button>
    </div>
  );
}
```

---

## Real-time Updates

### When to use which strategy

| Strategy | Latency | Complexity | Use when |
|----------|---------|-----------|----------|
| Polling | 5-60s | Low | Simple metrics, infrequent updates |
| SSE | <1s | Medium | Server-pushed updates, uni-directional |
| WebSocket | <100ms | High | Bi-directional, high-frequency (trading, live ops) |

### Polling

```typescript
function usePolledData<T>(url: string, intervalMs: number) {
  const [data, setData] = useState<T | null>(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(url);
      setData(await response.json());
    };

    fetchData(); // immediate first fetch
    const id = setInterval(fetchData, intervalMs);
    return () => clearInterval(id);
  }, [url, intervalMs]);

  return data;
}
```

### Server-Sent Events (SSE)

```typescript
function useSSE<T>(url: string) {
  const [data, setData] = useState<T | null>(null);

  useEffect(() => {
    const source = new EventSource(url);

    source.onmessage = (event) => {
      setData(JSON.parse(event.data));
    };

    source.onerror = () => {
      // Browser auto-reconnects SSE; log but don't crash
      console.warn('SSE connection lost, reconnecting…');
    };

    return () => source.close();
  }, [url]);

  return data;
}
```

### Reconnect logic (WebSocket)

```typescript
function useWebSocket(url: string) {
  const wsRef = useRef<WebSocket | null>(null);
  const [data, setData] = useState(null);

  const connect = useCallback(() => {
    wsRef.current = new WebSocket(url);

    wsRef.current.onmessage = (event) => setData(JSON.parse(event.data));

    wsRef.current.onclose = () => {
      // Exponential backoff reconnect
      setTimeout(connect, Math.min(1000 * 2 ** reconnectCount.current, 30_000));
      reconnectCount.current++;
    };

    wsRef.current.onopen = () => { reconnectCount.current = 0; };
  }, [url]);

  useEffect(() => { connect(); return () => wsRef.current?.close(); }, [connect]);

  return data;
}
```

### Optimistic updates

Update the UI immediately before the server confirms:

```typescript
const queryClient = useQueryClient();

const mutation = useMutation({
  mutationFn: updateMetric,
  onMutate: async (newValue) => {
    await queryClient.cancelQueries({ queryKey: ['metrics'] });
    const previous = queryClient.getQueryData(['metrics']);
    queryClient.setQueryData(['metrics'], (old) => ({ ...old, value: newValue }));
    return { previous };
  },
  onError: (_, __, context) => {
    queryClient.setQueryData(['metrics'], context?.previous);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['metrics'] });
  },
});
```

---

## Chart States

Every chart widget must implement all four states.

### Loading state — skeleton

```tsx
function ChartSkeleton() {
  return (
    <div className="animate-pulse">
      <div className="h-4 w-32 bg-gray-200 rounded mb-4" /> {/* title */}
      <div className="flex items-end gap-2 h-48">
        {[0.6, 0.9, 0.4, 0.7, 1, 0.5, 0.8].map((h, i) => (
          <div key={i} className="flex-1 bg-gray-200 rounded-t" style={{ height: `${h * 100}%` }} />
        ))}
      </div>
    </div>
  );
}
```

### Empty state — no data yet

```tsx
function EmptyChart({ title }: { title: string }) {
  return (
    <div className="flex flex-col items-center justify-center h-48 text-center">
      <ChartBarIcon className="h-10 w-10 text-gray-300" />
      <p className="mt-2 text-sm font-medium text-gray-500">No {title} data yet</p>
      <p className="text-xs text-gray-400">Data will appear once activity begins.</p>
    </div>
  );
}
```

### Error state — with retry

```tsx
function ChartError({ onRetry }: { onRetry: () => void }) {
  return (
    <div className="flex flex-col items-center justify-center h-48">
      <ExclamationTriangleIcon className="h-10 w-10 text-red-400" />
      <p className="mt-2 text-sm font-medium">Failed to load chart</p>
      <button onClick={onRetry} className="mt-3 btn-secondary text-sm">Retry</button>
    </div>
  );
}
```

### Partial data warning

```tsx
{data.isPartial && (
  <p className="text-xs text-amber-600 mt-1">
    Showing data through {format(data.lastUpdated, 'MMM d, HH:mm')} — real-time data may be delayed.
  </p>
)}
```

---

## Responsive Dashboard Layouts

### CSS Grid dashboard

```css
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1rem;
}

/* KPI cards — 3 per row on desktop, 2 on tablet, 1 on mobile */
.kpi-card {
  grid-column: span 4;
}
@media (max-width: 1024px) { .kpi-card { grid-column: span 6; } }
@media (max-width: 640px)  { .kpi-card { grid-column: span 12; } }

/* Main chart — 8 columns, sidebar — 4 columns */
.main-chart { grid-column: span 8; }
.sidebar    { grid-column: span 4; }
@media (max-width: 1024px) {
  .main-chart, .sidebar { grid-column: span 12; }
}
```

### Widget reorder on mobile

On mobile, stack widgets vertically in priority order (most important first). Do not rely on CSS order — explicitly set the visual order for mobile:

```css
@media (max-width: 640px) {
  .revenue-kpi { order: 1; }
  .users-kpi   { order: 2; }
  .main-chart  { order: 3; }
  .sidebar     { order: 4; }
  .detail-table { order: 5; }
}
```

---

## Checklist

- [ ] Primary KPIs at top (large number, trend %)
- [ ] Secondary charts in middle tier
- [ ] Detail tables below the fold
- [ ] Reading pattern matches audience (F-pattern for analysts, Z for executives)
- [ ] Global filters in page header; local filters inside widget
- [ ] Filter state serialized to URL params
- [ ] "No results" state with clear explanation and reset action
- [ ] Real-time strategy chosen based on latency requirement (polling/SSE/WebSocket)
- [ ] Reconnect logic for WebSocket
- [ ] Loading skeleton implemented for every chart widget
- [ ] Empty state implemented (distinct from error state)
- [ ] Error state with retry button
- [ ] Responsive CSS Grid layout (12-column)
- [ ] Mobile widget order matches importance priority

Related Skills

typography-design

8
from marvinrichter/clarc

Typography as a creative discipline: typeface selection criteria, type pairing (serif + sans, display + body), modular scale systems, line-height and tracking ratios, hierarchy construction, and web/mobile rendering considerations. The decisions behind design tokens, not the tokens themselves.

sdk-design-patterns

8
from marvinrichter/clarc

SDK design patterns — API ergonomics, backward compatibility (semantic versioning, deprecation), multi-language SDK generation (openapi-generator vs Speakeasy), error hierarchy design, SDK testing strategies, and documentation as first-class SDK artifact.

presentation-design

8
from marvinrichter/clarc

Presentation structure, narrative design, and slide layout principles. Covers the problem-solution-evidence arc, slide density rules (one idea per slide), slide type catalogue, opening hooks, and closing patterns. Use when structuring any slide deck — conference talk, demo, investor pitch, or team update.

marketing-design

8
from marvinrichter/clarc

Marketing asset design for developers: Open Graph / social media card specs and HTML generation, email template HTML/CSS patterns (table-based layout, Outlook compatibility, dark mode), banner and ad creative dimensions, print-safe PDF generation, and brand consistency across marketing touchpoints. From OG image code to email that renders in Outlook.

liquid-glass-design

8
from marvinrichter/clarc

iOS 26 Liquid Glass design system — dynamic glass material with blur, reflection, and interactive morphing for SwiftUI, UIKit, and WidgetKit.

generative-ai-design

8
from marvinrichter/clarc

AI-assisted design workflows: prompt engineering for image generation (Midjourney, DALL-E 3, Stable Diffusion, Flux), achieving style consistency across a generated asset set, post-processing AI outputs for production use, legal and licensing considerations, and when AI generation is and isn't appropriate. For teams integrating generative AI into their design workflow.

experiment-design

8
from marvinrichter/clarc

A/B testing and experimentation workflow: hypothesis design, metric selection, sample size calculation, statistical significance, common pitfalls (peeking, SRM, novelty effect), and experiment lifecycle. Complements feature-flags (implementation) with statistical rigor.

design-system

8
from marvinrichter/clarc

Design system architecture: design tokens (color, spacing, typography, radius), component library layers (Primitive → Composite → Pattern), theming with CSS Custom Properties and Tailwind, Storybook documentation, and dark mode. The foundation for consistent UI across an entire product.

design-ops

8
from marvinrichter/clarc

Design Operations: Figma file organization standards, design-to-dev handoff workflow, design QA checklist, design token sync pipeline (Figma Variables → Style Dictionary → CSS/Tailwind), design system versioning and governance, component audit methodology, and design-dev collaboration patterns. Bridges the gap between design tools and production code.

api-design-advanced

8
from marvinrichter/clarc

Advanced API design — per-language implementation patterns (TypeScript/Next.js, Go/net-http), anti-patterns (200 for everything, 500 for validation, contract breaking), and full pre-ship checklist.

api-design

8
from marvinrichter/clarc

REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs.

zero-trust-patterns

8
from marvinrichter/clarc

Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.