Vela UI Docsvenizia.ai

Storage dashboard design system

The taste-design doc for the storage dashboard pair (org Overview + admin Dashboard) - tokens and components from the Vela UI system, layout from the app shell, and a real-data contract from the storage data model.

The full spec

Design System: Storage Dashboard (Vela UI)

Skill: stitch-design-taste, adapted to Vela UI’s ratified system — velaui/catalog/DESIGN.md is the ground truth for tokens/typography/shapes; storage/ui/src is ground truth for the dashboard screen anatomy; storage/api/src/models/schema.ts is ground truth for every data value. Where this document and the taste template disagree, the shipped system wins.


Configuration — Style Dials

Dial Level Why
Creativity 6 An operations dashboard, not a marketing site. The identity is typographic precision, halftone meters and census-grade numbers — not expressive editorial layout.
Density 7 Cockpit-leaning: metric strips, dense tables, quota gauges. Breathing room comes from panel padding (24px) and section gaps, not from empty columns.
Variance 6 Structured asymmetry: a 1-column by-app breakdown beside a 2-column widget+usage stack, panels of unequal width — but always a regular grid underneath.
Motion 5 Restrained by system rule: hover tints, sheet/dialog transitions, skeleton-first loading. Spring physics where motion exists; no theatrical choreography.

1. Visual Theme & Atmosphere

A restrained, near-monochrome dashboard that reads like a well-typeset census report: near-black ink on paper-white, one teal accent used sparingly, flat 1px borders instead of shadows on every resting surface, and dotted halftone meters as the only decorative voice — usage rendered as fields of 3px dots rather than smooth bars. The atmosphere is quiet, precise, infrastructure-grade: every figure is a real counter from usage_counters (organizations, objects, stored bytes), never a marketing number. The two surfaces are the organization’s Overview (one bucket at a glance) and the platform administration Dashboard (totals across every organization); both share the app shell — an icon rail, an 80px header with breadcrumb, and panels of 24px padding, 12px radius, 1px border, no shadow.


2. Color Palette & Roles

Only the semantic layer is public. Light values below; dark mode is mechanical, never hand-authored (.dark swaps white→neutral-950 #0A0A0A and derives everything else via color-mix() — no bespoke dark palette).

Descriptive name Token Hex (light) Role
Paper bg-main #FFFFFF Page and every panel surface
Surface Tint bg-surface-1/2 #F2F2F2 / #E6E6E6 Hover fills, chip tags, meter tracks (5% / 10% ink mixes)
Ink primary-default / bg-foreground / text-strong #0A0A0A Primary buttons, table headers, headings, active nav, focus borders. Off-black — never #000000
Slate Text text-muted #525252 Descriptions, subtitles, secondary copy
Note Text text-note #A3A3A3 Placeholders, disabled, “as of” stamps, empty-state hints
Hairline border-default / input-default #E5E5E5 / #F5F5F5 1px structural lines; input fills
Signal Teal accent-default #14B8A6 THE one chromatic accent (H 176°, S 78% — under the 80% cap). Accent badges, accent focus rings on choice controls. Sparing use
Cyan Info info-middle / info-strong / info #06B6D4 / #0891B2 / #ECFEFF “public” and informational badges
Green Success success-middle / success-strong #22C55E / #16A34A Status “active”, copied confirmations
Amber Warning warning-middle / warning-strong #F59E0B / #D97706 Near-quota: meter + “near limit · 84%” badge
Red Negative negative-middle / negative-strong / negative #DC2626 / #991B1B / #FEF2F2 Over-quota, revoke, destructive actions, 507 refusals
Chart 1–5 chart-1..5 #3B82F6 #14B8A6 #F97316 #A855F7 #EC4899 Per-app halftone share tones only — never for text

Status tints are used as subtle badge surfaces with their -strong text step. Known gap, do not repeat: variant="subtle" color="info" (info-strong on info) measures 3.54:1 — below AA; check any new tint/text pairing against 4.5:1 before shipping.


3. Typography Rules

  • Family: Inter Display (variable, 100–900, the loaded static WOFF2 set). This is the system’s identity — do NOT substitute Geist/Satoshi/Outfit. The taste-template “no Inter” rule is overridden by the ratified system (see catalog/DESIGN.md §Typography).
  • Display/Headings: 32px / 600 / 1.2 page titles; panel titles 16px / 600 / 1.4; metric figures 24px / 600 / 1.3 with tabular-nums. Hierarchy through weight and color (text-strong vs text-muted), not through size escalation.
  • Body: 14px / 400 / 1.4 — the only reading size. Descriptions may drop to 12px / 1.3.
  • Labels: interactive labels semibold (600) — buttons 14/600; static data labels medium (500) — tags 14/500, badges 12/500. Metric captions: uppercase 10px / 500.
  • Mono (system monospace stack — font-mono): every identifier — bucket obj-8f3ac2d4, access keys STG…, sha256/etag, mono prefix filter values, timestamps in dense tables. Metric figures stay in Inter with tabular-nums; identifiers go mono. Density is 7, so mono wins for all keys and opaque tokens.
  • Banned: serifs anywhere (dashboard), pure-black body text, weight overloading in body copy (use color contrast instead), display sizes above 32px on dashboard surfaces.

4. Component Stylings

  • Buttons: six variants — primary (ink fill #0A0A0A, white label, hover lightens to #171717), secondary (#F5F5F5), outline (transparent, 1px border-default), ghost, destructive (10% negative fill + negative-middle label from rest — the ONLY resting-state-colored variant), link (no chrome at rest; a padded radiused hit box only on focus-visible). Radii by size tier (8px up to md 40px, 12px at xl 56px). Flat surfaces, no glow, no gradient; active gives tactile feedback (state scrim/1px press — never neon). Sizes 24/32/40/48/56.
  • Inputs: three variants — outlined (bordered, default), fill (input-default surface, borderless with a transparent 1px reserve so hover/focus never reflow), underline (bottom border only). Sizes sm 32px default / md / lg. Error state via aria-invalid: negative border + 25%-negative focus ring. Label above, helper optional, error below — no floating labels.
  • Badges & Tags: badge = pill (rounded-full), four variants (solid/subtle/outline/ghost) × seven colors; status vocabulary: active=success, revoked=neutral/negative, public =info, near limit=warning. Tag = bg-surface-1 chip, 8px radius, with selected as a 1px inset outline — background and outline never coexist.
  • Panels (the dashboard’s card): 24px padding, 12px radius, 1px border-default, bg-main, no shadow, 16px internal gap; header separated by a 16px-under-divider (border-b, 16px padding below). Panel title 16/600 + description 12 muted; floated actions (RefreshBar “As of HH:MM:SS · refresh”, Export CSV, quota actions) pin to the header’s right. Footer links: border-t divider + link button with a 16px arrow-right.
  • Metric (resource card block): uppercase 10px muted label, 24px semibold tabular-nums value 6px below, optional 3-row halftone meter, 12px muted hint below — the exact composition of the Platform usage card (Organizations · Objects · Stored).
  • Halftone meters: the signature viz — dots 3px on a 5px pitch (7×6px rows), filled share in a tone, rest in a 20%-ink tint. Tones: neutral (blue-600) at rest, warning ≥80%, danger ≥100%. Used for by-app shares and quota; never smooth gradient bars.
  • Quota gauges: thin role="progressbar" bar (1.5px track, bg-surface-2), fill tone by used fraction: primary-default < 80%, warning-middle 80–99%, negative-middle ≥ 100%; percentage label beside; near limit · N% warning badge or at limit — uploads are refused negative badge per the thresholds below.
  • Tables: 44px header row (12px medium muted labels), 56px data rows, 1px border-t dividers, no row striping; identifiers mono with a hover copy control that really copies and flips to “Copied”; actions as ghost icon buttons with tooltips; foreign-scope rows read read-only. On phones the table becomes a stacked card list (md:hidden duality).
  • Overlays: dialog = 12px radius, bg-main, border, shadow-md, 24px padding, icon slot (status fills, rounded-full 48px); sheet = edge-docked panel, 24px radius, shadow-xs; dropdown/popover/select = 12px surface + shadow-md, 6px-radius items with 5%-ink hover. Elevation is binary: resting surfaces have none, overlays carry md.
  • Loading: skeletons matching the final layout 1:1 (Tiles/List/Rows), not spinners.
  • Empty states: composed — 32px icon-note glyph, title, description, one sm action (e.g. over-quota’s all-clear “No organization is over its quota” check state).
  • Copy buttons: ghost xs, min 52px, navigator.clipboard.writeText + 1.6s green “Copied” flip; degrade silently where the clipboard rejects.

5. Layout Principles

  • App shell: 80px icon rail (folded) expanding to a grouped nav sidebar; the trigger sits on the rail’s edge. Rail ↔ expanded folds below 1024px. Header: 80px, breadcrumb (muted parent / strong page), notification bell with dot, light/dark segmented, plan card (hidden < lg). Main: p-6, px-8 ≥1200, gap-6 between stacked surfaces.
  • Grid: 12/10/4 columns at desktop/tablet/mobile; 80/48/20px margins; flat 20px gutter. Page content contained to ~1400px; the widest panel is 1372px. Use CSS Grid for structural layout; never flexbox calc() percentage math.
  • Dashboard anatomy (both modes follows the same grammar): page head (32px title + muted subtitle + primary CTA + refresh); then the data surfaces. Org Overview: 1-column By-app panel left, right stack of the bucket widget (128px, mono path chip + copy + primary Upload CTA) over the Usage panel — grid minmax(0,1fr) / minmax(0,1.25fr), stacking single-column below 1200px. Admin Dashboard: full-width Platform usage panel (three metrics) over the full-width By-organization table.
  • The metrics triad is data, not marketing — the banned “3 equal cards” rule targets marketing feature rows; a three-metric resource card (Organizations · Objects · Stored) is the app’s own composition and stays. Never build a second decorative triplet.
  • No overlapping elements; every value, chip and action occupies its own cell.
  • Full-height surfaces use min-h-[100dvh], never h-screen.

6. Motion & Interaction

The system animates rarely — encode restraint first, then physics where motion exists:

  • Defaults: hover = 5%-ink surface tint only (no glow, no scale). Buttons give a subtle press (scrim/1px). Focus rings are 3px ring-neutral-shadow with outline-colored border.
  • Overlays: sheet slides from the right (transform), backdrop fades (opacity); dialogs fade + slight scale on entry, ESC/backdrop/close all wired. Fold/unfold of the rail animates width via the system’s collapsible keyframes (ease-out), never layout props by hand.
  • Spring physics where a physical transition exists (toggle switches, sheet): stiffness: 100, damping: 20. No linear easing.
  • Loading: skeleton-first (layout-matching shimmer), never circular spinners. Lists mount immediately — no cascade delay on tables (operators need the data now); if a reveal is added, cap it at ≤ 12 rows, 60ms steps, transform/opacity only.
  • Perpetual loops: none shipped today, by design. If a live cue is ever needed (e.g. a syncing state), use a 1s opacity pulse of an existing status dot — no spinning, no floating icons.
  • Hardware rules: animate only transform and opacity; never top/left/width/height; keep any heavy animation in isolated leaf components; target 60fps.

7. Dashboards Have No Marketing Hero

The first impression is the page head + metric strip — treat it as the hero and own its discipline:

  • Head: 32px title (“Storage administration” / “Overview”), one muted subtitle line, one primary CTA at most (New access key / Upload / Raise quota), the RefreshBar stamp on the same row. No secondary “learn more” links, no scroll hints, no stacked illustration.
  • Asymmetric by construction: left-aligned title block, right-aligned actions, then the data grid — never a centered hero.
  • First data impression: the metric strip must be real counters (see the data contract) — “Organizations · Objects · Stored” for admin; “Stored / Objects / Free” + by-app shares for the org — never invented statistics.

8. Responsive Rules

  • < 768px: every multi-column layout collapses to one column; the objects table becomes the stacked card list; panels keep 16px padding; buttons may go full-width for the one primary CTA.
  • < 1024px: sidebar folds to the 80px rail; the plan card and wide header chrome hide.
  • < 640px: the rail becomes a slide-in drawer behind “Open menu” with a 44px trigger.
  • No horizontal overflow on mobile — tables fold to cards, identifiers truncate, chips wrap.
  • Typography: page titles scale (32px stays legible at 390px; never below 14px body).
  • Touch targets: primary actions ≤ 40px minimum (system md default); row icon actions stay 32px with adequate hit spacing per the system’s density — do not inflate table rows; mobile drawer targets 44px.
  • Viewports to verify: 375 / 390 / 768 / 1024 / 1440.

9. Real Data Contract (the anti-fabrication anchor)

Every figure a generated screen shows must come from this model — never invented:

Concept Real shape Notes
Organization uuid id (Shell’s), name string (synced) No slug, no created_at in the model
Bucket one per org, derived obj-<organizationId> Created lazily on first upload; is_public affects signed-link lifetime
Quota bytes_limit bigint bytes, nullable; note (e.g. Plan: Team (10 GiB)) NULL or missing row = UNLIMITED — not a plan-derived number; editor works in GiB (round(gib × 1024³))
Objects object_key (app-relative), app_slug (namespace), visibility ∈ {org, app, owner}, size_bytes (bytes), content_type, uploaded_via ∈ {rest,s3}, public_token (NULL = not public), created_at “Published” = public_token not null, not a boolean
Usage (org) OrgUsage { organizationId, organizationName, objectCount, bytesTotal, bytesLimit, usedFraction, apps: { appSlug, objectCount, bytesTotal }[] } usedFraction null when unlimited; apps sorted by bytesTotal desc
Usage (admin totals) Totals { organizations, objects, bytes } organizations counts only orgs with ≥1 usage row
Thresholds near = usedFraction ≥ 0.8 AND < 1; over = bytesTotal ≥ bytesLimit The “near limit · N%” and “at limit” badges are these predicates
Sizes formatBytes: binary units B/KiB/MiB/GiB/TiB/PiB, 1024-based; toFixed(2) < 10 else toFixed(1) Never decimal-GB marketing figures
Access keys access_key_id = STG + 20 of [A-Z0-9]; status ∈ {active,revoked}; description; last_used_at; secretAccessKey returned exactly once at creation Max 50 keys per org; listing never shows secrets
Failures 507 QUOTA_EXCEEDED (org full — detail bytesLimit/bytesUsed/bytesRequested), 413 PAYLOAD_TOO_LARGE (single-request ceiling, default 50 MiB) 507 copy is caller-facing by contract; multipart sidesteps 413, quota still enforced at complete
Signed links default 900s, clamp 60s–7d; public objects use the 7-day expiry Every link expires — show expiresAt, never a permanent URL

10. Anti-Patterns (Banned)

  • No fabricated data — any metric outside §9 (uptime %, response times, invented quotas) renders as [metric] placeholder, never a number.
  • No invented KPI sections (“SYSTEM PERFORMANCE”, “BY THE NUMBERS” cards) beyond the two sanctioned metric surfaces.
  • No generic placeholder identities — keep the blocks’ established Acme / Hiếu Võ sample personae, and every ID follows the real shapes (uuid orgs, obj-<uuid>, STG… keys).
  • No LABEL // YEAR formatting.
  • No marketing clichés (“Elevate”, “Seamless”, “Next-Gen”).
  • No emojis; no custom cursors; no scroll-hint copy (“Scroll to explore”) or bouncing chevrons.
  • No pure black #000000 — ink is #0A0A0A; no neon/outer glows; no gradients on surfaces or headline text; no oversaturated accents (teal at 78% S is the ceiling); no purple/blue neons.
  • No shadows on resting surfaces (cards, inputs, tables) — only overlays (md) and the sheet (xs).
  • No arbitrary radii — 6 / 8 / 12 / 24px + pill only (the 10px lg button corner is a verified one-off, not a tier).
  • No 3-equal-card rows except the data metrics triad; no centered heroes; no overlapping text/visuals; no h-screen; no flexbox percentage math; no primitive token usage (neutral-950 etc.) in app code — semantic tokens only.
  • No serif typography anywhere in the dashboard.