04 · Structure
Layout
Two kinds of surfaces, one system. Marketing and brand surfaces are dark-first — navy, atmospheric, editorial. Internal apps are light-first — white surfaces anchored by the navy gradient sidebar. This page is the canonical, framework-independent standard for the internal-app shell.
The shell
Anatomy of a DiffLab app
Every internal app is the same three regions: a 240px fixed gradient sidebar, a 56px white header with breadcrumbs, and a scrollable content area. This is the canonical DiffLab app shell (from the enterprise-workflows template) — it supersedes any legacy app style.
(2rem at ≥ 1024px)
grid-template-columns: 240px 1fr — the sidebar spans both rows; only the content scrolls.
Live reference
See it running
A complete, self-contained implementation — no framework, no build step, just the tokens file and one HTML page. Copy it as the starting point for any new internal app.
Specification
The numbers
Every value below is normative and available as a
--dl-* token in
tokens.json /
tokens.css.
Sidebar
| Property | Value | Token |
|---|---|---|
| Width | 240px, fixed |
--dl-layout-sidebar-width |
| Background | linear-gradient(167deg, #1A2744 0%, #0B1224 100%) |
--dl-color-app-sidebar-gradient |
| Text | rgba(255,255,255,0.84) |
--dl-color-app-sidebar-text |
| Text, muted / section labels | rgba(255,255,255,0.55) |
--dl-color-app-sidebar-text-muted |
| Active item background | rgba(245,158,11,0.18) (amber @ 18%) |
--dl-color-app-sidebar-active-bg |
| Active item indicator | inset 2px white left bar + white text (box-shadow: inset 2px 0 0 #fff) |
--dl-color-app-sidebar-active-indicator |
| Brand block | the mark rendered white at the top; user block pinned to the bottom | — |
Header & content
| Property | Value | Token |
|---|---|---|
| Header height | 56px (3.5rem) |
--dl-layout-header-height |
| Header treatment | white background, 1px bottom hairline, breadcrumbs left / actions right | — |
| Content padding | 1.5rem |
--dl-layout-content-pad |
| Content padding, ≥ 1024px | 2rem |
--dl-layout-content-pad-lg |
| Page background | paper #f8fafc; cards sit on it as white surfaces |
--dl-color-brand-paper |
Text ramp (light app surfaces)
| Role | Value | Token |
|---|---|---|
| Strong — headings, primary data | #111827 |
--dl-color-app-text-strong |
| Muted — labels, descriptions | #4B5563 |
--dl-color-app-text-muted |
| Faint — placeholders, tertiary | #9CA3AF |
--dl-color-app-text-faint |
Type conventions
| Convention | Value |
|---|---|
| Root font size | html { font-size: 106.25% } — 17px at the browser default |
| Line height | 1.55 for app UI text |
| Families | Inter for UI, Space Grotesk for headings, JetBrains Mono for code, IDs, and eyebrow labels |
| Numerals | font-variant-numeric: tabular-nums in every data table and stat |
Radii
| Element | Radius | Token |
|---|---|---|
| Card | 0.75rem (padding 1.25–1.5rem) | --dl-radius-card |
| Button | 0.375rem | --dl-radius-button |
| StatusBadge | 9999px (pill) | --dl-radius-badge |
Buttons — 4 variants
| Variant | Background | Text | Hover |
|---|---|---|---|
| Primary | amber #F59E0B |
white | #D97706 |
| Secondary | white, 1px border | #111827 |
border darkens to #9CA3AF |
| Ghost | transparent | #4B5563 |
faint gray fill |
| Danger | #DC2626 |
white | #B91C1C |
Buttons — 3 sizes
| Size | Padding | Font size |
|---|---|---|
| sm | 0.3rem 0.7rem | 0.75rem |
| md (default) | 0.45rem 0.95rem | 0.85rem |
| lg | 0.6rem 1.25rem | 0.95rem |
Status badges
| Status | Foreground | Background |
|---|---|---|
| Success | #0F7A0A | #E4FFEC |
| Error | #DC2626 | #FEE2E2 |
| Warning | #B45309 | #FEF3C7 |
| Info | #0085FF | #E0F2FF |
Recipes
Component recipes
Framework-free snippets — plain HTML and CSS over the tokens. Port them to React, Vue, or whatever your app uses; the numbers are what matter.
Card
White surface on the paper background. Radius 0.75rem, padding 1.25–1.5rem, hairline border.
<div class="card">…</div>
.card {
background: #ffffff;
border: 1px solid hsl(214 32% 91%);
border-radius: var(--dl-radius-card); /* 0.75rem */
padding: 1.25rem; /* 1.5rem for roomy cards */
}
Button
Radius 0.375rem, weight 600. Variants: primary, secondary, ghost, danger. Sizes: sm, md, lg.
<button class="btn btn--primary btn--md">New run</button>
.btn {
display: inline-flex;
align-items: center;
gap: 0.45em;
border: 1px solid transparent;
border-radius: var(--dl-radius-button); /* 0.375rem */
font: 600 0.85rem/1.2 var(--dl-font-family-sans);
cursor: pointer;
}
.btn--sm { padding: 0.3rem 0.7rem; font-size: 0.75rem; }
.btn--md { padding: 0.45rem 0.95rem; font-size: 0.85rem; }
.btn--lg { padding: 0.6rem 1.25rem; font-size: 0.95rem; }
.btn--primary { background: #F59E0B; color: #fff; } /* hover #D97706 */
.btn--secondary { background: #fff; color: #111827;
border-color: hsl(214 32% 91%); }
.btn--ghost { background: transparent; color: #4B5563; }
.btn--danger { background: #DC2626; color: #fff; } /* hover #B91C1C */
StatusBadge
Pill (radius 9999px): soft background, strong foreground. Four statuses only.
<span class="badge badge--success">Succeeded</span>
.badge {
display: inline-flex;
padding: 0.15rem 0.6rem;
border-radius: var(--dl-radius-badge); /* 9999px */
font-size: 0.72rem;
font-weight: 600;
}
.badge--success { color: #0F7A0A; background: #E4FFEC; }
.badge--error { color: #DC2626; background: #FEE2E2; }
.badge--warning { color: #B45309; background: #FEF3C7; }
.badge--info { color: #0085FF; background: #E0F2FF; }
SectionHeader
Title and description on the left, primary action on the right. One per content section.
<div class="section-header">
<div>
<h2>Workflow Runs</h2>
<p>Every execution across your pipelines.</p>
</div>
<button class="btn btn--primary btn--md">New run</button>
</div>
.section-header {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.section-header h2 {
font: 700 1.375rem/1.2 var(--dl-font-family-display);
letter-spacing: -0.03em;
color: #111827;
margin: 0;
}
.section-header p { margin: 0.2rem 0 0; font-size: 0.85rem; color: #4B5563; }