xess — the within.website design system

Skip to main content

The within.website design system — Gruvbox warmth, serif headlines, magenta-invert links, and one CSS file.

Welcome to xess, the design system I use for everything I ship on within.website. Gruvbox Pale palette, Podkova headlines, Schibsted Grotesk body, Iosevka Curly Iaso for code, and a box of templ components wired to a single stylesheet. This page is the living demo — every component rendered, every line of source one fold-open away.

I built it because I kept re-solving the same typography and color questions on every side project, and picking hex values at 2am is a special kind of cursed. Now I pick them once, commit them, and the rest of my life moves a little faster.

Colors

Gruvbox makes me happy. The palette is warm — nothing in it is a true neutral, every "gray" is yellow-leaning beige, and every accent hue has a muted and a bright variant so light mode and dark mode can pick opposite ends of the ramp and stay balanced.

The one pair you'll see everywhere is the link magenta — #b80050 in light mode, pink #ffa8ce in dark — inverting to cream-on-magenta on hover. Loud, a little weird, and the first thing visitors notice. I'm never changing it.

Surface & foreground ramp

--bg-hard
#f9f5d7
--bg-soft
#f2e5bc
--bg-0
#fbf1c7
--bg-1
#ebdbb2
--bg-2
#d5c4a1
--bg-3
#bdae93
--bg-4
#a89984
--fg-0
#282828
--fg-1
#3c3836
--fg-2
#504945
--fg-3
#665c54
--fg-4
#7c6f64

Accents (muted)

--red
#9d0006
--green
#79740e
--yellow
#b57614
--blue
#076678
--purple
#8f3f71
--aqua
#427b58
--orange
#af3a03

Accents (bright)

--red-bright
#cc241d
--green-bright
#98971a
--yellow-bright
#d79921
--blue-bright
#458588
--purple-bright
#b16286
--aqua-bright
#689d6a
--orange-bright
#d65d0e

Typography

Podkova is a warm slab-ish serif that pairs with the cream surfaces like butter on toast. Schibsted Grotesk carries the body copy — variable weight, quietly modern, and it gets out of the way. Iosevka Curly Iaso is the mono, though that face is custom-cut and served from files.xeiaso.net; the fallback chain lands on ui-monospace when you just pull in the stylesheet.

Scale is 14/16/18/20/24/30/36/48 pixels. Line-height 1.55 for prose. text-wrap: pretty on both headings and paragraphs. That's the entire typography ruleset, and I'd like to keep it that way.

Heading scale

Heading 1 — Podkova 36px

Heading 2 — 30px

Heading 3 — 24px

Heading 4 — 20px

Heading 5 — 18px
Heading 6 — tracked uppercase
Source
<h1>Heading 1 — Podkova 36px</h1>
<h2>Heading 2 — 30px</h2>
<h3>Heading 3 — 24px</h3>
<h4>Heading 4 — 20px</h4>
<h5>Heading 5 — 18px</h5>
<h6>Heading 6 — tracked uppercase</h6>

Body, emphasis, code, and keyboard keys

Schibsted Grotesk at 16px with line-height 1.55. Emphasis uses weight 600. Italics are the matching italic face. Inline code looks like http.ListenAndServe. Hit Ctrl + K to feel something.

Source
<p>Schibsted Grotesk at 16px with line-height 1.55.
<strong>Emphasis</strong> uses weight 600.
<em>Italics</em> are the matching italic face.
Inline code looks like <code>http.ListenAndServe</code>.
Hit @xess.Kbd(txt("Ctrl")) + @xess.Kbd(txt("K")) to feel something.</p>

Buttons

Five variants: primary, secondary, accent, ghost, danger. They all pill-shaped, they all lift 1px on hover, and they all live in the same CSS class. Primary is orange because orange is the signature CTA — the one time I tried a blue-primary button the site felt like a SaaS product, and I hated it.

Danger uses muted red because the bright variant fails WCAG AA against white text — I learned that the slow way. Hover states darken by one ramp step. That's the entire motion vocabulary of this design system.

All five variants

Source
@xess.Button(xess.BtnPrimary,   "", txt("Sponsor me"))
@xess.Button(xess.BtnSecondary, "", txt("Learn more"))
@xess.Button(xess.BtnAccent,    "", txt("Subscribe"))
@xess.Button(xess.BtnGhost,     "", txt("Dismiss"))
@xess.Button(xess.BtnDanger,    "", txt("Delete"))

Button as a link

Source
// Pass a non-empty href and Button renders as <a>.
@xess.Button(xess.BtnPrimary, "https://xeiaso.net", txt("Go to xeiaso.net"))

Cards, tags, badges

Cards use the bg-2 surface with a hairline border and a 6px radius. Tags are pill-shaped chips for post taxonomies or keyword lists — they inherit text color and shouldn't be expected to carry semantics. Badges are tags with meaning bolted on: info, success, warning, danger, plus a neutral default.

Card with content

The card

It's a box with a warm surface and a hairline border. Use it for anything that deserves a lift off the page but shouldn't shout about it.

Source
@xess.Card(cardInner())

templ cardInner() {
  <h3>The card</h3>
  <p>It's a box with a warm surface...</p>
}

TagList

gotemplgruvboxdesign-systemsminimalism
Source
@xess.TagList([]string{"go", "templ", "gruvbox", "design-systems", "minimalism"})

Badges across all kinds

neutralinfosuccesstipnotewarningdanger
Source
@xess.Badge(xess.BadgeNeutral, txt("neutral"))
@xess.Badge(xess.BadgeInfo,    txt("info"))
@xess.Badge(xess.BadgeSuccess, txt("success"))
@xess.Badge(xess.BadgeTip,     txt("tip"))
@xess.Badge(xess.BadgeNote,    txt("note"))
@xess.Badge(xess.BadgeWarning, txt("warning"))
@xess.Badge(xess.BadgeDanger,  txt("danger"))

Text scale

TextStrong, TextSubtle, TextMuted, TextCaption. These map to fg-0, fg-2, fg-3, fg-4. The scale exists because sometimes you need to step text down without leaning on <small> or rolling your own opacity, which nine times out of ten becomes technical debt before the week is out.

Four levels of text

Strong text sits on fg-0 with weight 600.

Subtle text steps down to fg-2 — still comfortably readable.

Muted text runs at fg-3 with a smaller step — footnotes, meta.

Caption text is the lowest contrast — fg-4, still AA compliant on cream.

Source
<p>@xess.TextStrong(txt("Strong text sits on fg-0..."))</p>
<p>@xess.TextSubtle(txt("Subtle text steps down to fg-2..."))</p>
<p>@xess.TextMuted(txt("Muted text runs at fg-3..."))</p>
<p>@xess.TextCaption(txt("Caption text is the lowest contrast..."))</p>

Surfaces & dividers

SurfaceRaised bumps a block up to bg-0, useful when a key section of a longer page wants a visible lift. DividerStrong is a 2px rule; BorderHairline is a 1px rule. That's all the division of content you get, on purpose — the rest of the page rhythm is whitespace and type.

Raised surface

A raised panel for when a section of the page really wants your attention but you don't want to reach for a Card.

Source
@xess.SurfaceRaised(surfaceInner())

Dividers

Above the strong divider.

Between divs.


Below the hairline.

Source
<p>Above the strong divider.</p>
@xess.DividerStrong()
<p>Between divs.</p>
@xess.BorderHairline()
<p>Below the hairline.</p>

Content primitives

Blockquote prefixes with a literal > because email-style quoting feels right on a blog. PullQuote is the one block allowed to have a colored left bar. Pre is <pre> styled like a Gruvbox terminal. CodeInline is the little chip. Kbd looks like a keyboard key. Mark highlights with the purple-bright swatch — the same swatch your browser uses for selection.

Blockquote

The best way to keep a secret is not to write it down. The second best way is to write it down in markdown and hide it in a gigantic static site.
Source
@xess.Blockquote(txt("The best way to keep a secret..."))

Pullquote

Source
@xess.Pullquote(txt("Good design systems feel inevitable..."))

Pre and CodeInline

Inline code like @xess.CodeInline(txt("go tool templ generate")) sits in the flow. Block code gets its own tile:

go get within.website/x/xess
// then in your templates:
@xess.Button(xess.BtnPrimary, "", txt("Hi"))
Source
@xess.CodeInline(txt("go tool templ generate"))
@xess.Pre(txt("go get within.website/x/xess\n// ..."))

Details fold

Tap to reveal the long code block
// the kind of thing you don't want inlined above the fold
func main() {
    internal.HandleStartup()
    // ...
}
Source
@xess.Details("Tap to reveal the long code block", detailsBody())

Mark and Kbd

Highlight somethingimportantinline.

Show a shortcut likeCtrl+Shift+P

Source
@xess.Mark(txt("important"))
@xess.Kbd(txt("Ctrl")) + @xess.Kbd(txt("Shift")) + @xess.Kbd(txt("P"))

Figure

Responsive <picture> with AVIF/WebP/JPG fallbacks and loading="lazy". Caption is italic, muted, centered. Figure is the one place on the site where imagery is allowed to go full-bleed, and the caption uses the same muted tone as all other meta text so it reads as supplementary.

Figure with caption

Cadey, having an &quot;aha&quot; moment
Figure 1. Cadey realising the stickers render through the Figure component too.
Source
@xess.Figure(
  "", "", "https://stickers.xeiaso.net/sticker/cadey/aha",
  "Cadey having an 'aha' moment",
  figureCap(),
)

Admonitions

Six flavors: info, warning, tip, note, danger, success. Each gets a colored left rule and a little accent dot next to the title. The body text stays on fg-1 because colored body text on a warm surface is a contrast nightmare, and I'd rather have the hue on the rule than pretend yellow text on cream is legible.

These exist because long-form posts sometimes need to say "look, here is the caveat" louder than regular prose can. Danger is reserved for actual danger — not "this might surprise you" but "this will delete your data." Don't burn the signal.

Info

Source
@xess.Admonition(xess.AdmonitionInfo, "Info",
    p("The link magenta hover is load-bearing..."))

Warning

Source
@xess.Admonition(xess.AdmonitionWarning, "Warning",
    p("Iosevka Curly Iaso is not bundled..."))

Tip

Source
@xess.Admonition(xess.AdmonitionTip, "Tip",
    p("Prefer the muted accent variants..."))

Note

Source
@xess.Admonition(xess.AdmonitionNote, "Note",
    p("The only decorative gradient..."))

Danger

Source
@xess.Admonition(xess.AdmonitionDanger, "Danger",
    p("Don't use left-border accent cards..."))

Success

Source
@xess.Admonition(xess.AdmonitionSuccess, "Success",
    p("If your prose fits in 65 characters per line..."))

Chat (Conv)

Character dialogue is the signature feature of the live site. A Conv block holds multiple ChatRow components; each row carries a 64x64 sticker avatar and a name-colored link. The rows share a bg-soft background and visually fuse into one surface. The avatar frame is a boxy 2px radius on purpose, because the sticker is the star.

Stickers are fetched live from stickers.xeiaso.net/sticker/{character}/{mood}. I didn't bundle them because there's a lot of them and because the live URLs work fine. When you're offline or the service is down, everything else on the page still renders — only the avatars 404.

A three-row Conv

Cadey drinking coffee
Cadey

So this is what the design system looks like when you run it.

Aoi looking confused
Aoi

Wait — the stickers are fetched live from stickers.xeiaso.net?

Cadey having an aha moment
Cadey

Yep. Saves on bundling. When the service is down, the avatars 404 but nothing else breaks.

Source
@xess.Conv(chatRows())

templ chatRows() {
  @xess.ChatRow(
    xess.StickerURL("cadey", "coffee"), "Cadey drinking coffee",
    "Cadey", "",
    p("So this is what the design system looks like..."))
  @xess.ChatRow(
    xess.StickerURL("aoi", "wut"), "Aoi looking confused",
    "Aoi", "",
    p("Wait — the stickers are fetched live..."))
  @xess.ChatRow(
    xess.StickerURL("cadey", "aha"), "Cadey having an aha moment",
    "Cadey", "",
    p("Yep. Saves on bundling..."))
}

Forms

Inputs, textareas, selects, checkboxes, radios, switches, labels, helper text, error text. No JS — the styling is pure CSS. The switch is semantically a checkbox wearing a sliding-track skin. The focus ring is the purple-bright accent at 2px, shared across every focusable element on the page.

Honestly, I don't love forms. Nobody does. But a design system with a glaring <input>-shaped hole is conspicuous, and real contact pages need real styling. Here it is.

A full contact form

Source
@xess.Form("/contact", "post", formInner())

templ formInner() {
  @xess.FieldGroup(nameField())
  @xess.FieldGroup(emailField())
  @xess.FieldGroup(messageField())
  @xess.Button(xess.BtnPrimary, "", txt("Send"))
}

Checkbox, radio, switch

Source
@xess.Checkbox("cb", "subscribe", "yes", "Keep me in the loop", true)
@xess.Radio("r1", "plan", "free", "Free", true)
@xess.Radio("r2", "plan", "pro",  "Pro",  false)
@xess.Switch("s1", "beta", "Opt into beta features", false)

Select

Source
@xess.Select("lang", "lang", []xess.SelectOption{
    {Value: "go",     Label: "Go",     Selected: true},
    {Value: "rust",   Label: "Rust"},
    {Value: "elixir", Label: "Elixir"},
    {Value: "nix",    Label: "Nix"},
}, false)

Helper and error text

Letters, numbers, and dashes. No spaces.

Source
@xess.FieldGroup(helperInner())

templ helperInner() {
  @xess.Label("hint", "Username")
  @xess.Input("hint", "user", xess.InputText, "", "", false)
  @xess.HelperText(txt("Letters, numbers, and dashes..."))
  @xess.ErrorText(txt("That username is already taken."))
}

Content layout

Prose wraps long-form text in a 65ch max-width column so your eyes aren't doing line-tennis across a 1440px display. Hero is the page-intro block with title, subtitle, and body slot (that's what the top of this page is). PageHeader is the leaner variant — title plus a meta row, meant for article pages. PostCard is for blog index pages, and PostMeta formats the date, author, reading-time, and tags row that sits under a post title.

PageHeader with PostMeta

Designing xess

Source
@xess.PageHeader("Designing xess", pageHeaderMeta())

templ pageHeaderMeta() {
  @xess.PostMeta("2026-04-24", "Xe Iaso", "5 min read",
    []string{"design-systems", "go", "templ"})
}

PostCard

Designing xess

Why I keep re-solving the same typography questions, and how xess stops the loop.

design-systemsgotempl
Source
@xess.PostCard(xess.PostCardProps{
    Href:    "/posts/designing-xess",
    Title:   "Designing xess",
    Excerpt: "Why I keep re-solving the same typography questions...",
    Date:    "2026-04-24",
    Tags:    []string{"design-systems", "go", "templ"},
    Hero:    "https://files.xeiaso.net/hero/santa-cruz-seals.webp",
    HeroAlt: "Seals piled on a rock at Santa Cruz",
})

Feedback & overlays

Toast for transient notifications, Modal for dialogs (using the native <dialog> element), Progress for long-running operations, Tooltip for hover hints. Modal and Toast both need JS to be actually useful — the design system gives you the styles and lets you wire the behavior yourself. I'm not shipping a framework, just opinions.

Toast — all four kinds

Heads up

The design system only uses two shadow sizes. Don't add a third.

Saved

Your settings were persisted. For real this time.

Careful

That action will email every sponsor on file.

Something broke

Couldn't reach stickers.xeiaso.net. Avatars will 404 until it's back.

Source
@xess.Toast(xess.ToastInfo,    "Heads up",        p("..."))
@xess.Toast(xess.ToastSuccess, "Saved",           p("..."))
@xess.Toast(xess.ToastWarning, "Careful",         p("..."))
@xess.Toast(xess.ToastDanger,  "Something broke", p("..."))

Modal opened by a trigger button

Delete this post?

Deleting a post is permanent. When you've linked to it from elsewhere, those links will 404. No amount of shouting at the backup will bring the post back once this finishes.

Source
// Trigger button points at the modal's id via data-modal-open.
// design.js calls showModal() on the matching <dialog> when clicked.
<button type="button" class="xe-btn xe-btn--danger" data-modal-open="demo-modal">
    Delete this post
</button>
@xess.Modal("demo-modal", "Delete this post?", false, modalBody())

Progress

Rebuilding search index
Source
@xess.Progress("0.64", "1", "Rebuilding search index")

Tooltip

Hover or focus the underlined term to see the tooltip:Gruvbox

Source
@xess.Tooltip(
    "Warm-neutral palette designed by Morhetz",
    txt("Gruvbox"))

Data display

DefinitionList for term and description pairs, Stat for big-number portfolio blocks, Timeline for career pages, Table with a horizontal-scroll wrapper so wide tables don't break mobile. When you're reaching for these, you're probably building an /about page or an experience timeline, and that's fine — those pages exist.

Stats

47 Components
35 Color tokens
11 Type scales
1 CSS file
Source
@xess.Stat("47", "Components")
@xess.Stat("35", "Color tokens")
@xess.Stat("11", "Type scales")
@xess.Stat("1",  "CSS file")

Definition list

Podkova
Serif typeface used for every heading. Weight 600.
Schibsted Grotesk
Variable sans-serif for body copy. 400 regular, 600 emphasis.
Iosevka Curly Iaso
Custom-cut monospace. Not bundled; falls back to ui-monospace.
Source
@xess.DefinitionList([]xess.Definition{
    {Term: "Podkova",            Description: "Serif typeface for headings..."},
    {Term: "Schibsted Grotesk",  Description: "Variable sans-serif..."},
    {Term: "Iosevka Curly Iaso", Description: "Custom-cut monospace..."},
})

Timeline

  1. 2018
    xeiaso.net v1

    Hugo-based. Blog posts in markdown. Looked like a 2018 tech blog.

  2. 2022
    Switch to Lume

    Deno-based static site generation with JSX components. Conv and Picture were born.

  3. 2024
    xess extracted

    Design language pulled out of the site as a reusable package.

  4. 2026
    cmd/design.within.website

    The demo page you're reading right now.

Source
@xess.Timeline([]xess.TimelineEvent{
    {When: "2018", Title: "xeiaso.net v1",  Body: p("Hugo-based...")},
    {When: "2022", Title: "Switch to Lume", Body: p("Deno-based...")},
    {When: "2024", Title: "xess extracted", Body: p("Design language...")},
    {When: "2026", Title: "cmd/design.within.website", Body: p("The demo...")},
})

Table

TokenValueUsed for
--radius-xs2pxSticker avatar frames
--radius-sm4pxInline code
--radius-md6pxCards, details, pre, admonitions
--radius-lg8pxTags, blockquotes
--radius-xl12pxPill buttons
Source
@xess.Table(tableInner())

templ tableInner() {
  <thead><tr><th>Token</th><th>Value</th><th>Used for</th></tr></thead>
  <tbody>
    <tr><td><code>--radius-xs</code></td><td>2px</td><td>Sticker avatar frames</td></tr>
    ...
  </tbody>
}