DOU DESIGN

Interface design guidelines

01 / PRINCIPLES

Principles

A page shows the content people need to read and the actions they need to take. Color, spacing, and motion have fixed roles and do not change from page to page.

01

Content first

Numbers and useful information lead the eye. Decoration appears only when it carries meaning.

02

Consistent order

Use the same spacing, alignment, and grouping throughout. The same meaning always gets the same visual treatment.

03

Warm restraint

Soft sage green, generous corners, and plain language keep a precise data interface approachable.

Do

Build hierarchy with weight, size, and space.

Avoid

Neon colors, page gradients, glows, and decoration without a purpose.

02 / FOUNDATIONS

Foundations

A shared visual grammar keeps the experience consistent across platforms and screen sizes. Every foundation value comes from a semantic token.

02.1

Color

A black night theme and a warm paper theme share the same semantics. Changing the theme changes surfaces and contrast, not layout.

NightDark
Default theme
Today's intake1,248kcal
Canvas #000000
PaperLight
Light theme
Today's intake1,248kcal
Canvas #F4EFE5

Accent presets

A preset replaces the accent only. Text, surfaces, and status colors stay the same. Select one to preview it across the site.

Core semantics

Select a swatch to copy its value. Never communicate status with color alone; pair it with text, shape, or an icon.

02.2

Typography

A calm text face paired with compact numerals. Regular 400 and semibold 600 cover most hierarchy.

What did you eat today1,248kcal
Meican SansFallback: system sans-serifNumerals: tabular figures
RoleSize / line heightWeightTracking
Large title38 / 1.04600−1.3
Page title32 / 1.08600−0.9
Section title21 / 1.20600−0.4
Body16 / 1.504000
Footnote13 / 1.504000

Align numbersMeasurements and dates use tabular figures. Units are smaller and share the baseline.

Limit line lengthBody text does not stretch indefinitely. When text is enlarged, let it wrap instead of clipping.

02.3

Layout & spacing

A 4px rhythm. Space follows content relationships instead of being divided evenly.

4
8
12
16
24
42
24 pxMobile page inset
42 pxSection gap
24 pxDefault card padding
760 pxProduct content width

Narrow screens stay single column. Wide screens cap the reading width and center the content. Reflow multi-column controls before reducing type. Fixed actions must not cover inputs, and scrollable content leaves about 144px behind a floating navigation bar.

02.4

Shape & depth

Rounded corners are soft without becoming playful. Thin borders define surfaces; shadows only suggest a slight spatial relationship.

28 pxPrimary card
18 pxStandard input
10 pxBadge
PillCapsule control
Do

Use a 1px translucent card border and 0.7px dividers.

Avoid

Outlining every nested element or stacking heavy shadows.

02.5

Motion

Motion confirms input and preserves spatial continuity. It is not a demonstration of technique. Nothing loops, and entrances do not queue.

PRESS170 msscale 0.97 · opacity 0.90
Selection240 ms
Segmented thumb300 ms
Page entrance440 ms

Respect the system “Reduce motion” setting. Replace spatial movement with an immediate state change or a quick fade.

03 / COMPONENTS

Components

A finite set of composable variants covers most product needs. Every specimen below is a real control and can be used directly.

03.1

Buttons

For submitting, confirming, and navigating. One primary action per area; everything else is secondary, outline, quiet, or glass.

VariantsPRIMARY / SECONDARY / OUTLINE / QUIET / GLASS
Sizes & states40 / 48 / 56 PX
Default height48 px
Primary height56 px
Target size≥ 44 px

Press scales to 0.97 with no ripple. Loading keeps the original width. Disabled buttons do not respond. Use the glass variant only over an existing surface or divider.

View button code
<button class="dd-button primary">Save changes</button>
<button class="dd-button secondary">Add item</button>
<button class="dd-button outline">Outline</button>
<button class="dd-button glass">Glass</button>
<button class="dd-button secondary" disabled>Unavailable</button>

<style>
.dd-button { min-height: 48px; padding: 0 20px; border-radius: 999px;
  border: 1px solid transparent; font: 600 14px/1 sans-serif; }
.primary { background: #f5f5f7; color: #000; }
.secondary { background: rgba(255,255,255,.07); color: #f5f5f7;
  border-color: rgba(255,255,255,.12); }
.outline { background: transparent; color: #f5f5f7;
  border-color: rgba(255,255,255,.22); }
.glass { background: rgba(255,255,255,.04); color: #c2d6c1; }
.dd-button:active { transform: scale(.97); opacity: .9; }
.dd-button:disabled { opacity: .4; cursor: not-allowed; }
</style>
03.2

Segmented controls

Segmented controls handle a small set of exclusive options. Selection chips handle filters that people scan quickly.

Meal segmentSELECT ONE
SelectedBreakfast
386 kcal

The selected state changes both the surface and aria-pressed. Use segments for 2–4 exclusive options. Use chips when there are more filters.

View selection code
<div class="dd-segment" role="group" aria-label="Meal">
  <button aria-pressed="true">Breakfast</button>
  <button aria-pressed="false">Lunch</button>
  <button aria-pressed="false">Dinner</button>
</div>
<button class="dd-chip" aria-pressed="true">Light activity</button>

<style>
.dd-segment { display: flex; gap: 3px; padding: 4px;
  border-radius: 15px; background: rgba(255,255,255,.06); }
.dd-segment button { flex: 1; min-height: 38px; border: 0;
  border-radius: 11px; background: transparent; color: #a8a8ad; }
.dd-segment [aria-pressed="true"] { background: #f5f5f7; color: #000; }
.dd-chip { min-height: 40px; padding: 0 16px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.12); background: transparent; }
.dd-chip[aria-pressed="true"] { background: #f5f5f7; color: #000; }
</style>
03.3

Switches & checks

A switch applies a setting immediately. Checkboxes select multiple items. Radio buttons choose one from a group.

The entire row is clickable. Label the setting itself, not the act of toggling. A radio group always keeps one option selected.

View toggle code
<label class="dd-switch">Automatic sync
  <input type="checkbox" checked><i aria-hidden="true"></i>
</label>
<label class="dd-check"><input type="checkbox" checked><i></i>Breakfast</label>

<style>
.dd-switch { display: flex; align-items: center; justify-content: space-between;
  min-height: 48px; cursor: pointer; }
.dd-switch input { position: absolute; opacity: 0; }
.dd-switch i { width: 48px; height: 28px; padding: 3px; border-radius: 999px;
  background: #3a3a3c; transition: background .2s; }
.dd-switch i::after { content: ""; display: block; width: 22px; height: 22px;
  border-radius: 50%; background: #fff; transition: transform .2s; }
.dd-switch input:checked + i { background: #a8bfa8; }
.dd-switch input:checked + i::after { transform: translateX(20px); }
.dd-switch input:focus-visible + i { outline: 2px solid #c2d6c1; outline-offset: 3px; }
.dd-check { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; }
.dd-check input { width: 20px; height: 20px; accent-color: #a8bfa8; }
</style>
03.4

Inputs

A persistent label sits above the field; placeholder text never replaces it. Units and values share a baseline.

Energy converterkJ ÷ 4.184 = kcal
kJ

Enter the kilojoule value per serving. The result updates automatically.

Equals239kcal

Focus changes the border and cursor only. An error message sits next to its field and never clears what was entered. Disabled fields remain readable.

View input code
<label for="energy">Energy from the nutrition label</label>
<div class="dd-field"><input id="energy" type="number" value="1000"><span>kJ</span></div>
<small>Enter the kilojoule value per serving.</small>
<div class="dd-field transparent"><input type="text" value="Rice"></div>
<div class="dd-field error"><input type="email" value="name@" aria-invalid="true"></div>

<style>
.dd-field { min-height: 54px; display: flex; align-items: center; gap: 10px;
  padding: 0 16px; border: 1px solid rgba(255,255,255,.12);
  border-radius: 17px; background: rgba(255,255,255,.05); }
.dd-field:focus-within { border-color: #c2d6c1; }
.dd-field input { flex: 1; min-width: 0; border: 0; outline: 0;
  background: transparent; color: #f5f5f7; font: 16px sans-serif; }
.dd-field.transparent { background: transparent; }
.dd-field.error { border-color: #e29379; }
.dd-field input:disabled { opacity: .45; }
</style>
03.5

Search & sliders

Search fields offer a clear action. Sliders adjust a value within a known range and always show the current value.

Common searches
150 g
0 g500 g

Sliders support arrow keys and expose a readable value. Pair a slider with a number field when exact input matters.

View search and slider code
<div class="dd-search"><input type="search" placeholder="Search foods"></div>
<label for="portion">Portion <output id="value">150 g</output></label>
<input id="portion" type="range" min="0" max="500" step="10" value="150"
  oninput="document.querySelector('#value').value = this.value + ' g'">

<style>
.dd-search { padding: 0 16px; border-radius: 17px;
  background: rgba(255,255,255,.05); }
.dd-search input { width: 100%; min-height: 52px; border: 0; outline: 0;
  background: transparent; color: #f5f5f7; }
input[type="range"] { width: 100%; height: 5px; accent-color: #a8bfa8; }
</style>
03.6

Cards & lists

Related content shares one parent surface. Rows use thin dividers; do not give every row its own card.

Grouped list28 PX RADIUS
Default cardFill and thin border
Outline cardTransparent background
Flat cardFill only

Primary label first, supporting detail below, value at the end. Dividers align with the text column. Clickable rows provide hover, focus, and pressed states.

View card and list code
<section class="dd-card">
  <button class="dd-row"><span>Breakfast<small>08:20 · 3 foods</small></span><strong>386 kcal</strong></button>
  <button class="dd-row"><span>Lunch<small>12:35 · 4 foods</small></span><strong>562 kcal</strong></button>
</section>
<section class="dd-card outline">Outline card</section>
<section class="dd-card flat">Flat card</section>

<style>
.dd-card { padding: 0 16px; border: 1px solid rgba(255,255,255,.12);
  border-radius: 24px; background: #141416; }
.dd-row { width: 100%; min-height: 68px; display: flex; align-items: center;
  justify-content: space-between; border: 0; background: transparent; color: #f5f5f7; }
.dd-row + .dd-row { border-top: 1px solid rgba(255,255,255,.1); }
.dd-row small { display: block; margin-top: 4px; color: #a8a8ad; }
.dd-card.outline { background: transparent; }
.dd-card.flat { border-color: transparent; }
</style>
03.7

Badges & avatars

Badges show a category or short status. Avatars identify a person, group, or source.

BadgesSEMANTIC / OUTLINE / GLASS
DefaultCompletePendingIssueOutlineGlassSyncing
Avatars32 / 40 / 56 PX
DDM
DMA+2

Keep status badges to one or two words. Use initials when there is no photo, and never rely on color alone to identify members.

View badge and avatar code
<span class="dd-badge success">Complete</span>
<span class="dd-badge outline">Outline</span>
<span class="dd-avatar">D</span>

<style>
.dd-badge { display: inline-flex; padding: 5px 9px; border-radius: 10px;
  background: rgba(255,255,255,.07); color: #f5f5f7; font-size: 11px; }
.dd-badge.success { background: rgba(168,191,168,.14); color: #c2d6c1; }
.dd-badge.outline { background: transparent; border: 1px solid rgba(255,255,255,.18); }
.dd-avatar { width: 40px; height: 40px; display: inline-grid; place-items: center;
  border-radius: 50%; background: rgba(168,191,168,.14); color: #c2d6c1; }
</style>
03.8

Progress & loading

Show a value when progress is known. Use an indeterminate bar or skeleton while the wait is unknown, with no layout shift.

Daily target69%
1,248 / 1,800 kcal
Over target112%
2,016 / 1,800 kcal
SyncingUnknown
Merging the latest records

The progress bar is 6px high with a 3px radius. When reduced motion is on, the indeterminate bar and spinner become static.

View progress code
<div class="dd-progress" role="progressbar" aria-valuenow="69" aria-valuemin="0" aria-valuemax="100">
  <i style="width:69%"></i>
</div>
<span class="dd-spinner" role="status" aria-label="Loading"></span>

<style>
.dd-progress { height: 6px; border-radius: 3px; background: rgba(255,255,255,.07); }
.dd-progress i { display: block; height: 100%; border-radius: inherit; background: #a8bfa8; }
.dd-spinner { width: 22px; height: 22px; border: 2px solid rgba(255,255,255,.12);
  border-top-color: #a8bfa8; border-radius: 50%; animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .dd-spinner { animation: none; } }
</style>
03.9

Data visualization

Charts sit below the value and its interpretation. Remove extra grid lines and axes so the trend reads immediately.

This week's average1,642kcal / day
7 days logged
Weight trend68.4kg
7 days −0.4
10/3011/05
Current periodPrevious periodTarget

Provide a text summary and exact values. Label the limits of estimated data next to the estimate.

View chart code
<figure><figcaption>This week's average: 1,642 kcal / day</figcaption>
  <svg viewBox="0 0 320 116" role="img" aria-label="Weight down 0.4 kilograms over seven days">
    <path d="M4 32C80 28 100 62 155 58S250 78 316 74"
      fill="none" stroke="#a8bfa8" stroke-width="2.4"/>
    <circle cx="316" cy="74" r="5" fill="#a8bfa8"/>
  </svg>
</figure>
<div class="dd-bars" aria-label="Seven-day intake bar chart">
  <i style="height:62%"></i><i style="height:82%"></i><i style="height:68%"></i>
</div>

<style>
figure { margin: 0; } figcaption { margin-bottom: 16px; color: #a8a8ad; }
.dd-bars { height: 118px; display: flex; align-items: flex-end; gap: 8px; }
.dd-bars i { width: 16px; border-radius: 5px; background: #a8bfa8; }
</style>
03.10

Data tables

For comparing rows of the same kind. Numbers align right, headers stay visible, and narrow screens scroll horizontally.

Recent records
FoodAmountCaloriesStatus
Rice150 g232 kcalLogged
Chicken breast300 g495 kcalLogged
Broccoli200 g126 kcalDraft

Tables are for comparison, not for arranging ordinary forms. Do not hide important actions behind hover. On phones, switch to a grouped list.

View table code
<div class="dd-table-scroll"><table>
  <thead><tr><th>Food</th><th>Amount</th><th>Calories</th></tr></thead>
  <tbody><tr><th scope="row">Rice</th><td>150 g</td><td>232 kcal</td></tr></tbody>
</table></div>

<style>
.dd-table-scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; text-align: left; }
th, td { padding: 14px 16px; border-bottom: 1px solid rgba(255,255,255,.1); }
thead th { color: #a8a8ad; font-size: 12px; font-weight: 400; }
td:last-child, th:last-child { text-align: right; font-variant-numeric: tabular-nums; }
</style>
03.11

Feedback & overlays

Inline alerts carry persistent information. Toasts report a result. Dialogs interrupt for a decision. Bottom sheets add mobile actions.

Demo data only

Records here are not uploaded or saved.

Sync complete

Every device is up to date.

One field is missing

Enter a target weight to continue.

Tooltips add a short clarification and never contain a required action. Confirm only irreversible actions; routine saves do not need repeated prompts.

View feedback code
<div class="dd-alert" role="status">Every device is up to date.</div>
<button id="open">Open dialog</button>
<dialog id="confirm"><h2>Delete this record?</h2><p>This cannot be undone.</p>
  <button onclick="confirm.close()">Cancel</button><button>Delete</button>
</dialog>
<div class="dd-toast" role="status">Changes saved</div>

<style>
.dd-alert { padding: 14px 16px; border: 1px solid rgba(168,191,168,.2);
  border-radius: 16px; background: rgba(168,191,168,.1); color: #c2d6c1; }
dialog { max-width: 360px; padding: 24px; border: 1px solid rgba(255,255,255,.16);
  border-radius: 32px; background: #141416; color: #f5f5f7; }
dialog::backdrop { background: rgba(0,0,0,.72); }
.dd-toast { padding: 13px 18px; border-radius: 18px; background: #1c1c1e; }
</style>

04 / PATTERNS

Patterns

Navigation, accessibility, and tokens. Platforms may change how an action is performed, but component states and reading order stay the same.

04.1

Navigation

Mobile uses a four-item floating capsule navigation with an inset pill for the selected item. Wide screens keep the same order and state.

SizeThe floating bar is 66px high and 296–420px wide. Touch targets are at least 44px.

HierarchyDo not combine multiple navigation systems at one level. Keep content clear of the floating area.

View navigation code
<nav class="dd-tabs" aria-label="Primary">
  <a href="/today" aria-current="page">Today</a><a href="/records">Records</a>
  <a href="/trends">Trends</a><a href="/me">Me</a>
</nav>
<nav class="dd-page-tabs" aria-label="Filter">
  <button aria-pressed="true">All</button><button aria-pressed="false">Unread</button>
</nav>

<style>
.dd-tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  width: 390px; max-width: 100%; padding: 6px; border-radius: 999px; background: #000; }
.dd-tabs a { min-height: 54px; display: grid; place-items: center;
  border-radius: 999px; color: #a8a8ad; text-decoration: none; }
.dd-tabs [aria-current="page"] { background: #1c1c1e; color: #c2d6c1; }
.dd-page-tabs { display: flex; gap: 24px; border-bottom: 1px solid rgba(255,255,255,.12); }
.dd-page-tabs [aria-pressed="true"] { color: #c2d6c1; border-bottom: 2px solid #a8bfa8; }
</style>
04.2

Accessibility

Accessibility is part of every component, not a separate review step.

01

Visible focus

Use a deep sage focus ring. Focus order follows reading order.

02

Enough contrast

Check text contrast on the actual composited surface, not on theoretical translucent values.

03

More than color

Pair color with text, shape, icons, and status wording.

04

User preferences

Support text scaling, reduced motion, keyboard input, and screen readers.

04.3

Design tokens

Color, type, spacing, radius, motion, and component sizes are provided as platform-neutral JSON.

design-tokens.json
{
  "color": {
    "dark": {
      "background": "#FF000000",
      "ink": "#FFF5F5F7",
      "accent": "#FFA8BFA8"
    }
  },
  "radius": {
    "card": 28,
    "field": 18,
    "pill": 999
  },
  "motion": {
    "press": { "durationMs": 170, "scale": 0.97 }
  }
}

Reuse an existing semantic role before adding one. When a new role is necessary, define its dark and light values, purpose, and surface instead of hard-coding a color in a component.

Search guidelines

Type to filter sections. Press Esc to close.

Delete this record?

This cannot be undone. Related statistics will update as well.

Choose a meal

You can change this after saving.

Done