The Editable Design System
A shared starting point for Editable. Simple styles, ready to use and adapt.
Foundations
Colors, fonts, and corner radii come from the shared tokens in src/app.css. These specimens follow the current token values.
Colors and surfaces
Keep each surface with its foreground color. Neutral controls use the muted surface on hover and a 10% foreground tint on press. Primary actions and editing actions use their respective accent treatments.
--background / --foreground
Secondary surface
Supporting text
--muted / --muted-foreground
--accent / --accent-foreground
--editing / --editing-foreground
--editing-muted / --editing
Borders and dividers
--stroke
Font families
Clear words.
font-sans · Inter
Clear words.
font-serif · Libertinos Serif Display
Clear words.
font-mono · IBM Plex Mono
Corner radii
Buttons and pill shells use the button radius. Popovers cap it at 1rem; their inner rows and pill controls subtract 4px padding and a 1px border, clamped at zero. Images use their own radius. Set either token to zero for square corners.
--button-border-radius
Button radius with an inset interior.
Button radius capped at 1rem, with an inset row.
--image-border-radius
Page limiter
Center each block with mx-auto max-w-7xl, then add px-5 sm:px-7 inside it. The width limit is 80rem (1280px), including side padding: 1.25rem (20px) on
small screens and 1.75rem (28px) from the 40rem (640px) breakpoint, at the default root font
size.
Page content aligns here.
The shaded edges show the shared page padding. Resize the window to compare.
<div class="mx-auto max-w-7xl">
<div class="px-5 sm:px-7">
Page content
</div>
</div> Keep full-width backgrounds outside the limiter and choose vertical spacing per block. Narrower reading columns can sit inside this shared page width. Apply the side padding once so adjacent blocks line up.
Content tracks
A track controls how much of the page limiter a block occupies and where it sits. Prose and descriptive listings share narrow left, center, and right tracks, plus a wide layout. Alternating their positions adds variation while keeping shared alignment edges.
Narrow tracks use max-w-4xl (56rem / 896px) inside the max-w-7xl page limiter (80rem / 1280px). These are maximum widths, not fixed proportions. Both include
the shared side padding.
max-w-4xlmx-auto max-w-4xlml-auto max-w-4xlVertical block spacing
Generous spacing separates sections. Compact spacing pulls related blocks together. Each block has its own top and bottom padding, chosen independently.
- A standalone block has generous padding above and below.
- In a section, the first block has generous top padding and compact bottom padding.
- Middle blocks have compact padding above and below.
- The last block has compact top padding and generous bottom padding.
- A section containing just one block has generous padding above and below.
Padding per edge
| Viewport width | Generous | Compact |
|---|---|---|
| Below 640px | 2.5rem · 40px | 1rem · 16px |
| 640–767px | 3.5rem · 56px | 1rem · 16px |
| 768–1023px | 4rem · 64px | 1rem · 16px |
| 1024px and above | 7rem · 112px | 1.75rem · 28px |
Pixel equivalents assume the default 16px root font size. Adjacent padding adds up: two compact edges create a 32px gap, or 56px from 1024px. Two generous edges create an 80px, 112px, 128px, or 224px gap across the same viewport ranges.
Standalone block
Independent content
Three blocks in one section
First block
Middle block
Last block
Shaded bands show the padding at its actual size. Resize the window to compare the scale. Spacing within a block, such as the gap between a heading and a paragraph, is a separate choice.
Sample block implementation
A top-level block receives its section context through mark, aliased to section. As in Prose.svelte, the block derives its top and bottom padding from that context.
No extra padding props are needed.
Sample prose block
<script lang="ts">
import { Node, NodeArrayProperty } from 'svedit';
import type { DocumentPath, NodeArrayAttachmentContext } from 'svedit';
let {
path,
mark: section = null
}: {
path: DocumentPath;
mark?: NodeArrayAttachmentContext | null;
} = $props();
let padding_top_generous = $derived(!section || section.is_start);
let padding_bottom_generous = $derived(!section || section.is_end);
</script>
<Node {path} class="bg-(--background) text-(--foreground)">
<div class="mx-auto max-w-7xl">
<div class={[
'px-5 sm:px-7',
padding_top_generous ? 'pt-block-generous' : 'pt-block-compact',
padding_bottom_generous ? 'pb-block-generous' : 'pb-block-compact'
]}>
<NodeArrayProperty
path={[...path, 'body']}
class="flex flex-col gap-5 sm:gap-7"
/>
</div>
</div>
</Node>- No section mark: both edges are generous.
section.is_start: the top edge is generous.section.is_end: the bottom edge is generous.section.is_middle: both edges are compact; the two boundary checks already cover this case.- A one-block section is both the start and the end, so both edges are generous.
The app uses shared utilities in src/app.css for the default rhythm: pt-block-generous, pt-block-compact, and their pb- equivalents. They apply the values above to existing elements without adding a wrapper component.
For a custom rhythm, replace the default utility on that edge with explicit responsive
classes, such as pt-8 lg:pt-16. Avoid combining a default utility with overrides on the same edge. Keep deliberate
exceptions local: flush and full-bleed figures have no vertical padding.
Typography
A shared scale for headings and body text.
display-1
Make room for what matters.
display-2
Make room for what matters.
display-3
Make room for what matters.
display-4
Make room for what matters.
display-5
Make room for what matters.
body-xl
Good design makes everyday things easier to understand. Clear words, comfortable spacing, and a thoughtful hierarchy help people find what they need and decide what to do next.
body-lg
Good design makes everyday things easier to understand. Clear words, comfortable spacing, and a thoughtful hierarchy help people find what they need and decide what to do next.
body-base
Good design makes everyday things easier to understand. Clear words, comfortable spacing, and a thoughtful hierarchy help people find what they need and decide what to do next.
body-sm
Good design makes everyday things easier to understand. Clear words, comfortable spacing, and a thoughtful hierarchy help people find what they need and decide what to do next.
Inline formatting
Emphasis and detail within the flow of a sentence.
Emphasis · em
Start with what people actually need.
Bold · strong
Keep the most important information easy to find.
Link · a
Explore the typography styles to see how the scale works.
Code · code
Use font-medium for button labels.
Highlight · mark
Highlight the part worth remembering, not the whole paragraph.
Descriptive listing
A linked row with a title, description, and optional metadata. Hover or press anywhere on the row to strengthen the title underline; keyboard focus outlines the whole row. A plain listing uses the same recipe with the description omitted.
Boxed items
Boxed items use a muted surface. Their secondary buttons use the background color at rest and the muted color on hover. This styling applies only to secondary buttons inside the box.
Editor pills
Compact surfaces for tools, actions, and previews.
Toolbar
Inline call to action
Variant selector and select parent
Link preview
Editor forms
Link and media prompts use an opaque surface with 4px padding, no shadow, and the button radius capped at 1rem. Fields and actions subtract the padding and border from that radius. Text inputs and textareas use 16px text and an editing-color border on focus.
Page browser
A muted editing fill marks the result Enter will open. Only keyboard-focused rows get a ring, drawn inside the row to keep tree guides and adjacent rows clear. Search focus uses a thin editing-color border. Action menus reuse the list popover recipe.
List popovers
Compact lists with a shared surface and aligned rows.