Web App Style Guide
Vue 3 + Vite + Tailwind CSS v4 · Light theme
Visual reference for the TrainerDay web app (trainerday.com). All tokens come from
src/assets/css/tailwind.css.
Design Tokens
Primitive values that define the visual language: colors, typography, spacing, and radii.
Colors
Primary
Semantic
Surfaces
Text
Training Zones
Periodization
Typography
The quick brown fox jumps over the lazy dog
Heading 1 (text-3xl font-bold)
Heading 2 (text-2xl font-bold)
Heading 3 (text-xl font-semibold)
Heading 4 (text-lg font-semibold)
Body text (text-base)
Muted text (text-sm text-text-muted)
Light small text (text-xs text-text-light)
Heading .td-h2 (1.5rem / 700)
Heading .td-h3 (1.25rem / 700)
Heading .td-h4 (1.125rem / 700)
Links
Border Radius
6px
12px
16px
100px
Components
Reusable UI elements built from design tokens.
Page Header
New — 2026-04-19 Full-width white band with a tab-style title. Used at the top of every top-level page (My Activities, My Plans, My Workouts, Workout Search, Today, Training Plans). Consistent whether the page has one tab or many — a single-title page still reads as an "active tab" so the visual rhythm across pages stays the same.
Anatomy: white band pulled up to cover the grey page gap (margin-top: -24px; padding-top: 32px; margin-bottom: 32px), bottom border, left-aligned inside .container. Each tab: px-4 py-3 text-[13px] uppercase font-bold tracking-[0.06em], active gets text-primary + 3px primary bottom border.
Single title (most pages)
Even though there's only one tab, keep the tab styling — active underline and all — so the page feels identical in rhythm to multi-tab pages.
Multiple tabs
Active tab gets the blue text + 3px primary underline; inactive tabs are muted with a transparent underline that stays reserved so nothing jumps when the active state moves.
HTML reference
<section class="page-top bg-white border-b border-border">
<div class="container">
<div class="flex w-full">
<!-- Single title: swap button for span, always active -->
<span class="px-4 py-3 text-[13px] uppercase font-bold tracking-[0.06em]
text-primary border-b-[3px] border-primary">
My Activities
</span>
</div>
</div>
</section>
<style>
.page-top {
margin-top: -24px; /* pull up to cover the grey gap */
padding-top: 32px;
margin-bottom: 32px;
}
</style>
Do / Don't
- ✓ Use on every top-level page so the vertical rhythm stays consistent.
- ✓ Keep the tab styling even with a single title — no separate
<h1>variant. - ✓ Left-align inside
.containerso the title lines up with page content.
- ✗ Don't use a standalone
.page-title<h1>at the top of a page anymore — it's been replaced by this pattern. - ✗ Don't center the tabs — they're left-aligned.
- ✗ Don't skip the negative
margin-top— the white band is supposed to cover the grey page gap.
Cards
Card Title
Content inside .td-card. The primary container pattern for app sections.
Base Card Title
Content inside .td-base-card. Same as td-card but without box-shadow.
Builder Card (Selected)
Featured card with blue ring and badge. Based on Coach Jack's plan builder pattern.
- ✓ Feature one
- ✓ Feature two
- ✓ Feature three
Training Zones Card
Interactive Card
Hover to see the border and shadow effect. Used for clickable cards.
Learn more →342
▲ 12% from last week
Form Inputs
Updated — 2026-04-19 All form controls now use the fully-rounded pill shape (border-radius: 100px) to match buttons. Horizontal padding bumped to 18px so text doesn't crowd the rounded ends. Focus / error / disabled visuals unchanged.
Applies to text, email, number, password, search, select, and textarea inputs. Textareas keep a softer 20px radius since a pill on a multi-line box looks wrong.
Your Functional Threshold Power
This field is required
Supports vertical resize
Dropdowns
Custom-styled native selects, now pill-shaped (rounded-pill) to match the rest of the form controls. Use appearance-none + a chevron SVG positioned absolutely.
Dropdown Menu
Action menu with .dd-action-item items.
Segmented Tabs
iOS-style segmented control. Container has surface bg + border. Active tab: white bg + subtle shadow. Inactive: muted text.
Badges
Zone Badges .dominant-zone
Pill Badges
Split Pill (Label + Value)
Pill with a gray label section on the left and a bold value on the right. Clickable variant gets hover:border-primary.
Readout Pill
New — 2026-04-19 Pill-shaped container with a small uppercase label floating in a notch on the top border, and one or more label/value columns inside separated by thin vertical dividers.
When to use: Contextual readouts that summarize the currently selected, focused, or active item — the selected row in a data grid, the active interval in a workout, the currently highlighted segment on a chart. Not for static KPIs (use a card) or primary metrics (use large numeric type).
Variants
Anatomy
- Outer pill — 1px border, fully rounded, 40px height, white background.
- Top-notch label — 9px uppercase, tracked, muted color; white background overlaps the border (rendered via
::before+data-label). - Each column — small uppercase label + bold tabular-numeric value (or a colored badge).
- Dividers — 1px × 18px, centered vertically between columns.
HTML reference
<div class="td-readout-pill" data-label="Selected row">
<div class="td-readout-col">
<span class="td-readout-label">Watts</span>
<span class="td-readout-value">125</span>
</div>
<div class="td-readout-divider"></div>
<div class="td-readout-col">
<span class="td-readout-label">Zone</span>
<span class="td-readout-badge" style="background:#7F7F7F;color:#fff">Z1</span>
</div>
</div>
Do / Don't
- ✓ Use near the thing it describes (above a grid, next to a chart).
- ✓ Keep total width modest — it's a readout, not a dashboard.
- ✗ Don't use for static facts or headers.
- ✗ Don't stack multiple readout pills in a row — pick the one that matters.
- ✗ Don't put interactive elements inside it — it's a display component.
Alerts
Info
Your training plan has been updated with the latest changes.
Success
Your workout has been saved to the calendar.
Warning
Your FTP has not been updated in over 8 weeks. Consider retesting.
Error
Could not connect to the server. Please try again.
Tabs
Underline Tabs
Active tab: primary color + 2px bottom border. Uppercase, bold, tracking-wide.
Pill Tabs
Active = primary-light bg + primary text. Inactive = no bg.
Toggles / Switches
40x22px pill track with 16px circle thumb. Primary blue for ON state.
Two-State Toggle (Label Pair)
Table
| Week | Phase | Hours | TSS | Zone |
|---|---|---|---|---|
| 1 | Base | 6.5h | 285 | Endurance |
| 2 | Base | 7.0h | 310 | Tempo |
| 3 | Build | 8.0h | 380 | Threshold |
| 4 | Peak | 5.5h | 420 | VO2max |