Design system

Style Guide

Every token, component and markdown element used on Daniela and Will Travel, rendered with the live stylesheet. Copy the markup, keep the site consistent.

Principles

Four rules that keep the site feeling like one place.

01

Photographs carry the page

Big images, generous white space, no decorative borders. If a section feels flat, give the photo more room rather than adding ornament.

02

One accent, used sparingly

Terracotta marks the single most important action in a view. Indigo does the everyday work. Everything else is ink on paper.

03

Reading comes first

Body copy sits in a 760px column at a comfortable 1.7 line height. Images and embeds widen out; text never does.

04

Tokens, not values

Never hard-code a colour, size or radius. Every value in the stylesheet resolves to a custom property so dark mode and rebrands stay cheap.

Colour

The palette comes straight from the hand-drawn logo: indigo ink, terracotta sun, blush, cream and mist. Brand tokens are raw values — always reference the semantic tokens in components so dark mode keeps working.

Brand

--brand-indigo #5761a2
--brand-indigo-deep #3c4478
--brand-indigo-ink #232741
--brand-terracotta #e99253
--brand-terracotta-deep #c9702f
--brand-blush #e1b09d
--brand-cream #fbede3
--brand-mist #d3dae8

Semantic

These are the ones to use. They swap automatically in dark mode.

--surface page background
--surface-alt tinted sections, cards
--surface-invert footer, dark blocks
--ink body text
--ink-muted secondary text
--ink-faint meta, captions
--accent primary accent
--accent-deep accent, text-safe
--accent-wash accent background
--link links
--border hairlines
--border-strong inputs, dividers
--footer-surface footer, dark in both themes
--footer-heading footer column headings

Signal

--signal-info notices
--signal-success do
--signal-warning warnings
--signal-danger errors, don't

Contrast

Terracotta on white does not pass AA for body text — use --accent-deep whenever the colour carries text, and reserve --accent for fills, rules and large display type.

Typography

Albert Sans does all the work: black weights with tight tracking for display, regular for reading, and small uppercase with wide tracking for labels. Bad Script — the logo lettering — appears only as an occasional flourish.

TokenFamilyUsed for
--font-displayAlbert SansHeadings, buttons, labels, nav
--font-bodyAlbert SansBody copy and everything else
--font-scriptBad ScriptWordmark, hero kicker. Never body copy.
--font-monoFira CodeCode samples

Scale

Fluid from 320px to 1500px. Never set a font size in px.

--step-7 · hero title · 800

A long way from home

--step-6 · h1 · 800

Nine months in Europe

--step-4 · h2 · 800

Latest travel guides

--step-3 · article h2 · 800

How to get there

--step-2 · h3 / card title · 800

What to look for in a tour

--step-1 · lede · 400

Everything we wish somebody had told us before we booked the flight.

--step-0 · body · 400 · 1.7

There is no way to arrive directly in Komodo — instead you will need to fly or ferry into Labuan Bajo, a city on a nearby island.

--step--1 · meta, captions

By Daniela and Will · 8 May 2025 · 9 minutes to read

--step--2 · eyebrow · 700 · uppercase

Fresh off the road

Fresh off the road

Latest travel guides

Everything we wish somebody had told us before we booked the flight.

Welcome to our travel blog

<p class="eyebrow">Fresh off the road</p>
<h2>Latest travel guides</h2>
<p class="lede">Everything we wish somebody had told us before we booked the flight.</p>
<p class="script" style="font-size: var(--step-2);">Welcome to our travel blog</p>

Spacing

One fluid scale for gaps, padding and rhythm. Pairs like --space-m-l grow between two steps as the viewport widens — use those for page gutters and section padding.

--space-3xs
--space-2xs
--space-xs
--space-s
--space-m
--space-l
--space-xl
--space-2xl
--space-3xl
TokenWhere it is used
--gutterLeft/right page padding on every shell
--width-page1200px — default page shell
--width-wide1440px — heroes, full-bleed sections, article images
--width-content760px — the reading column
--measure68ch — maximum line length for standalone copy

Radius & shadow

Soft but not bubbly. Cards and images use --radius-m; heroes and panels use --radius-l; anything interactive and pill-shaped uses --radius-pill.

xs
s
m
l
xl
pill
xs
s
m
l

Buttons

Pill-shaped, uppercase, wide tracking. One accent button per view — everything else is indigo, outline or ghost.

<div class="cluster">
    <a class="button" href="#">Read our story</a>
    <a class="button button--accent" href="#">Browse destinations</a>
    <a class="button button--outline" href="#">See all tools</a>
    <a class="button button--ghost" href="#">Keep reading</a>
</div>
<div class="cluster">
    <a class="button button--small" href="#">Small</a>
    <a class="button" href="#">Default</a>
    <a class="button button--large" href="#">Large</a>
    <a class="button" href="#" aria-disabled="true">Disabled</a>
</div>
<div class="cluster">
    <a class="button button--invert" href="#">On a photo</a>
    <a class="button button--accent" href="#">Primary</a>
</div>

Eyebrows & tags

The eyebrow is the small uppercase kicker that sits above almost every heading on the site. Tags are for post categories.

Where we've been

The archive

southeast asia itinerary planning japan
<p class="eyebrow">Where we've been</p>
<p class="eyebrow eyebrow--muted">The archive</p>
<div class="cluster" style="margin-top: 1rem;">
    <span class="tag">southeast asia</span>
    <span class="tag tag--accent">itinerary</span>
    <span class="tag tag--solid">planning</span>
    <a class="tag" href="#">japan</a>
</div>
<ul class="tag-cloud">
    <li><a class="tag tag--accent" href="#" aria-current="page">Southeast Asia<span class="tag__count">6</span></a></li>
    <li><a class="tag" href="#">Itinerary<span class="tag__count">3</span></a></li>
    <li><a class="tag" href="#">Planning<span class="tag__count">8</span></a></li>
    <li><a class="tag" href="#">DIY<span class="tag__count">2</span></a></li>
    <li><a class="tag" href="#">VPN<span class="tag__count">1</span></a></li>
</ul>
  • By Daniela and Will
  • 9 minutes to read
<ul class="meta-line">
    <li>By Daniela and Will</li>
    <li><time datetime="2025-05-08">8 May 2025</time></li>
    <li>9 minutes to read</li>
</ul>

Sections & grids

Pages are built from .section bands. Each holds a .shell for the max width and gutters, a .section-header, then content. Add .section--tinted to alternate the background.

Where we've been

Pick a corner of the world

Two continents down, plenty to go.

<section class="section section--tinted">
    <div class="shell">
        <div class="section-header section-header--center">
            <p class="eyebrow">Where we've been</p>
            <h2 class="section-header__title">Pick a corner of the world</h2>
            <p>Two continents down, plenty to go.</p>
        </div>
        <div class="grid">
            <!-- cards go here -->
        </div>
    </div>
</section>
ClassBehaviour
.gridAuto-fill columns, min 17.5rem — the default 3-up card grid
.grid--2Wider minimum (22rem) for 2-up layouts
.grid--4Narrower minimum (13.5rem) for dense tile walls
.feature-rowOne large card beside a stacked column of small ones
.clusterWrapping inline row — buttons, tags, meta
.stackVertical rhythm between direct children

Cards

One card component, four shapes. The title link stretches across the whole card, so don't nest other links inside a card body.

Aerial shot of the Komodo Islands

Indonesia

Komodo Island Tour

Everything you need to know before taking a tour around the Komodo Islands!

8 May 2025

<article class="card">
    <div class="card__media">
        <img src="/assets/img/posts/asia/komodocover.jpg" alt="Aerial shot of the Komodo Islands">
    </div>
    <div class="card__body">
        <p class="eyebrow">Indonesia</p>
        <h3 class="card__title"><a href="#">Komodo Island Tour</a></h3>
        <p class="card__excerpt">Everything you need to know before taking a tour around the Komodo Islands!</p>
        <p class="card__meta">8 May 2025</p>
    </div>
</article>
<article class="card card--horizontal">
    <div class="card__media">
        <img src="/assets/img/posts/asia/lombokcover.JPEG" alt="A waterfall in the hills of Lombok">
    </div>
    <div class="card__body">
        <h3 class="card__title"><a href="#">Lombok by Motorbike</a></h3>
        <p class="card__excerpt subtle">Everything you need to know before riding around Lombok!</p>
    </div>
</article>
<article class="card card--overlay">
    <div class="card__media">
        <img src="/assets/img/posts/europe/IMG_7233.jpg" alt="Orange sunset over a Norwegian harbour">
    </div>
    <div class="card__body">
        <p class="eyebrow">Itinerary</p>
        <h3 class="card__title"><a href="#">Nine Months in Europe</a></h3>
        <p class="card__excerpt">How we planned and executed our entire nine month Europe trip.</p>
    </div>
</article>
<article class="card card--boxed">
    <div class="card__body">
        <p class="eyebrow">Tool</p>
        <h3 class="card__title"><a href="#">How to Use the Budget Sheets</a></h3>
        <p class="card__excerpt">Track every euro and rupiah so the money lasts as long as the trip does.</p>
    </div>
</article>

Destination tiles

Tall, image-led links used for continents and countries. Keep labels to two lines.

<div class="grid grid--4">
    <a class="tile" href="#">
        <img src="/assets/img/posts/asia/cover.jpg" alt="Mountain road in northern Vietnam">
        <span class="tile__label">
            <span class="tile__count">6 guides</span>
            Southeast Asia
        </span>
    </a>
    <a class="tile" href="#">
        <img src="/assets/img/posts/europe/IMG_7233.jpg" alt="Orange sunset over a Norwegian harbour">
        <span class="tile__label">
            <span class="tile__count">2 guides</span>
            Europe
        </span>
    </a>
</div>

Heroes

Three header treatments: a photographic hero for the home page, a tinted text header for pages, and a centred editorial header for posts.

A winding coastal road seen from above
Welcome to our travel blog

Two people, a long way from home.

Guides, budgets and hard-won lessons from Europe and Southeast Asia.

<section class="hero hero--center hero--compact">
    <div class="hero__media">
        <img src="/assets/img/en.jpg" alt="A winding coastal road seen from above">
    </div>
    <div class="hero__inner">
        <div class="hero__content">
            <span class="hero__script">Welcome to our travel blog</span>
            <h1 class="hero__title">Two people, a long way from home.</h1>
            <p class="hero__lede">Guides, budgets and hard-won lessons from Europe and Southeast Asia.</p>
            <div class="hero__actions">
                <a class="button button--accent" href="#">Browse destinations</a>
                <a class="button button--invert" href="#">Meet Daniela &amp; Will</a>
            </div>
        </div>
    </div>
</section>

Design system

Style Guide

A tinted header for standard pages — supplied automatically by the page layout.

<header class="page-hero">
    <div class="page-hero__inner">
        <p class="eyebrow">Design system</p>
        <h1 class="page-hero__title">Style Guide</h1>
        <p class="page-hero__lede">A tinted header for standard pages — supplied automatically by the page layout.</p>
    </div>
</header>

Komodo Island Tour

  • By Daniela and Will
  • 9 minutes to read
<header class="post-hero">
    <div class="post-hero__inner">
        <ol class="breadcrumb">
            <li><a href="/">Home</a></li>
            <li>asia</li>
        </ol>
        <h1 class="post-hero__title">Komodo Island Tour</h1>
        <p class="post-hero__byline">Everything you need to know before taking a tour around the Komodo Islands!</p>
        <ul class="meta-line post-hero__meta">
            <li>By Daniela and Will</li>
            <li><time datetime="2025-05-08">8 May 2025</time></li>
            <li>9 minutes to read</li>
        </ul>
    </div>
</header>

Callouts & notices

Notices are generated automatically from markdown blockquotes. Callouts are hand-written for tips, packing lists and budgets.

Tip: Arrive in Labuan Bajo at least one day before with enough time to get everything sorted.


Warning: The national park entry fee is not always included in the tour price.


> ❕ Tip: Arrive in Labuan Bajo at least one day before with enough time to get everything sorted.

> ⚠ Warning: The national park entry fee is not always included in the tour price.

What to pack

Reef-safe sunscreen, a dry bag and more cash than you think you need — there is no ATM on the boat.

<div class="callout callout--accent">
    <p class="callout__title">What to pack</p>
    <p>Reef-safe sunscreen, a dry bag and more cash than you think you need — there is no ATM on the boat.</p>
</div>

Our verdict

Worth every rupiah. Book the overnight tour and sleep on the boat.

<div class="callout callout--invert">
    <p class="callout__title">Our verdict</p>
    <p>Worth every rupiah. Book the overnight tour and sleep on the boat.</p>
</div>

Table of contents

Long guides open with a jump list. Wrap the heading and list in .toc.

<div class="toc">
    <h3>Table of Contents</h3>
    <ul>
        <li><a href="#get-there">How to Get There</a></li>
        <li><a href="#tour">What to Look for in a Tour</a></li>
        <li><a href="#length">How Many Days</a></li>
        <li><a href="#route">The Route</a></li>
    </ul>
</div>

Forms

Pill-shaped inputs to match the buttons. Always pair an input with a visible label.

We only email when there's a new guide.
That doesn't look like an email address.
<div class="stack">
    <div class="field">
        <label class="field__label" for="sg-email">Email address</label>
        <input class="input" id="sg-email" type="email" placeholder="you@example.com">
        <span class="field__hint">We only email when there's a new guide.</span>
    </div>
    <div class="field">
        <label class="field__label" for="sg-select">Destination</label>
        <select class="select" id="sg-select">
            <option>Southeast Asia</option>
            <option>Europe</option>
        </select>
    </div>
    <div class="field">
        <label class="field__label" for="sg-message">Message</label>
        <textarea class="textarea" id="sg-message" placeholder="Ask us anything"></textarea>
    </div>
    <div class="field">
        <label class="field__label" for="sg-error">Email address</label>
        <input class="input input--invalid" id="sg-error" type="email" value="not-an-email">
        <span class="field__error">That doesn't look like an email address.</span>
    </div>
</div>

Newsletter band

The closing block on the home page. Use the inverted variant when it follows a light section.

<div class="newsletter">
    <p class="eyebrow">Stay in touch</p>
    <h2 class="newsletter__title">New guides, straight from the road</h2>
    <p class="newsletter__lede">We post when we've actually got something worth saying.</p>
    <form class="newsletter__form">
        <div class="field">
            <label class="field__label screen-reader-text" for="sg-news">Email address</label>
            <input class="input" id="sg-news" type="email" placeholder="you@example.com">
        </div>
        <button class="button button--accent" type="submit">Subscribe</button>
    </form>
    <p class="newsletter__note">No spam. Unsubscribe whenever.</p>
</div>

Markdown & prose

Everything inside a post body is wrapped in .prose. It is a named-line grid: text sits in the content column (760px), figures and embeds widen to wide (1440px), and anything with .bleed spans the full viewport. Plain markdown needs no wrapper divs.

How to get there

There is no way to arrive directly in Komodo — instead you will need to fly or ferry into Labuan Bajo, a city on a nearby island. There are many flight options available, leaving from Jakarta, Bali, or Lombok, so pick whatever fits best with your itinerary.

Getting around once you land

Prices in person are much better than online — just make sure you have enough cash. We ended up getting an overnight, semi-private tour for 1,600,000 IDR per person.

  • One day tours feel rushed and you miss sunset.
  • Overnight tours include a bed on the boat.
  • Ask about the park fee before you pay.
  1. Arrive in Labuan Bajo a day early.
  2. Walk the main street and compare three operators.
  3. Pay in cash, get a receipt.

We would do this again in a heartbeat — but we would book the overnight boat.

Daniela

A smaller heading

Inline code looks like --space-m, and highlighted text uses the accent wash. Small print sits in a smaller size.


A muted closing note, set with .subtle.

<article class="hentry post">
    <header class="post-hero">…</header>
    <div class="entry-content prose">
        <!-- markdown output drops straight in here -->
    </div>
    <footer class="post-footer">…</footer>
</article>

Images

Always use the image shortcode — it generates AVIF/WebP, responsive sizes and the figure wrapper. Inside a post it automatically widens past the text column.

Aerial shot of islands surrounded by beaches and blue water
Beautiful aerial shot of the Komodo Islands
Like our photos? Find them on Shutter stock .

&lt;Figure
    src="/assets/img/posts/asia/komodocover.jpg"
    alt="Aerial shot of islands surrounded by beaches and blue water"
    sizes="100vw"
    caption="Beautiful aerial shot of the Komodo Islands"
/&gt;

Photo credit overlay

Passing a caption also makes the shortcode inject the Shutterstock credit chip — the .bottom-left-text pill in the bottom-right of the photo, linking to both of our portfolios. It is an affiliate placement, so don't remove it and don't skip the caption on our own photographs. A captioned figure becomes a two-row grid (image, then caption) with the chip sharing the image cell, which keeps it inside the photo no matter how long the caption runs. It hides below 48rem, where it would cover too much of the picture.

ColumnWhat lands there
contentParagraphs, headings, lists, quotes, callouts — the default
widefigure, embeds and any element with .wide
fullElements with .bleed — edge-to-edge, use rarely

Tables & code

Tables scroll horizontally on small screens rather than squashing. Code blocks use the indigo-ink background from the syntax theme.

RouteDurationCost (IDR)
Labuan Bajo → Padar4 hours1,600,000
Padar → Pink Beach1 hourincluded
Park entry fee600,000
<table>
    <thead>
        <tr><th>Route</th><th>Duration</th><th>Cost (IDR)</th></tr>
    </thead>
    <tbody>
        <tr><td>Labuan Bajo &rarr; Padar</td><td>4 hours</td><td>1,600,000</td></tr>
        <tr><td>Padar &rarr; Pink Beach</td><td>1 hour</td><td>included</td></tr>
        <tr><td>Park entry fee</td><td>&mdash;</td><td>600,000</td></tr>
    </tbody>
</table>
.card__title a {
    color: inherit;
    text-decoration: none;
}
.card__title a {
    color: inherit;
    text-decoration: none;
}

Post front matter

The layouts read these keys. byline and heroImage are what make a post look right in the card grids — never ship a post without them.


---
title: Komodo Island Tour
date: 2025-05-08
author: Daniela and Will
tags:
 - asia
 - southeast-asia
 - planning
draft: false
byline: Everything you need to know before taking a tour around the Komodo Islands!
heroImage: /assets/img/posts/asia/komodocover.jpg
heroImageAlt: Aerial shot of islands surrounded by beaches and blue water
seo:
  title: The Best Way to Visit the Komodo Islands in 2025
  description: Everything you need to know before taking a tour from Labuan Bajo.
  changeFrequency: yearly
---
KeyWhere it shows up
titlePost header, card titles, browser tab
bylinePost lede and every card excerpt
heroImage / heroImageAltCard media on the home page, countries page and related posts
tagsBreadcrumb (first tag) and the tag pills in the post footer
seo.title / seo.descriptionTitle tag, meta description, Open Graph
eyebrowPages only — the kicker above the page title

Localisation

The site is built bilingual (English and Spanish). Posts live under src/content/posts/<lang>/ and pages under src/pages/<lang>/; every string of interface chrome comes from src/data/translations.ts. Never hard-code UI text in a layout or component — a Spanish reader sees the same template you do.

HelperUse it for
t(lang)Any interface string. t(lang)('footer.explore'). Supports substitution: t(lang)('post.by', { author }). A missing key throws at build time rather than rendering empty.
localeUrlInternal links. Rewrites /about/ to /en/about/ for the current language.
localeFallbackUrlFor pages that exist in only one language. Returns the current language's URL when that page is translated, and the default language's when it is not, so a link can never 404 from the other locale.
localeLinksThe translations of the current URL, for hreflang tags and the language switcher.
getPosts(lang)Posts in one language, newest first. Drafts are included by astro dev and excluded from astro build.
formatDate(date, lang)Localised dates, formatted in UTC so a bare YYYY-MM-DD never slips a day.

&lt;!-- a link to a page that exists in every language --&gt;
&lt;a href={localeUrl('/about/', lang)}&gt;&#123;t(lang)('header.home')&#125;&lt;/a&gt;

&lt;!-- a link to an English-only page, safe from every locale --&gt;
&lt;a href={localeFallbackUrl('/affiliates/', lang)}&gt;
    &#123;t(lang)('footer.affiliates')&#125;
&lt;/a&gt;
EN ES FR
<div class="switch-language">
    <a href="#" lang="en" hreflang="en" aria-label="EN: English" aria-current="page" class="current">EN</a>
    <a href="#" lang="es" hreflang="es" aria-label="ES: Spanish">ES</a>
    <span aria-label="FR: French" class="disabled">FR</span>
</div>

Adding a string

Add the key to both the en and es blocks of src/data/translations.ts, then reference it with t(lang). TypeScript checks the two trees against each other, and a lookup that misses throws during the build — so they cannot silently drift apart.

Do & don't

The mistakes that will make a new page look off-brand.

Do

Use --accent-deep when terracotta carries text.

Don't

Set body copy in --accent — it fails contrast on white.

Do

Give every section an eyebrow and a heading.

Don't

Stack two tinted sections in a row — alternate them.

Do

Reach for the image shortcode for every photograph.

Don't

Drop in a bare <img> — it skips the responsive sizes.

Do

Use the script face for one short flourish per page.

Don't

Set headings or paragraphs in Bad Script — it is unreadable at length.

Do

Add new values as tokens in variables.css and document them here.

Don't

Hard-code a hex, px size or radius inside a component.

Do

Put interface text in translations.js and pull it with translate.

Don't

Type English chrome straight into a layout — it leaks onto the Spanish pages.