Why we grind our own ink
Bottled ink is convenient, but grinding a stick against the stone is part of the lesson. It takes about five minutes, and it slows you down before the first stroke.
One stroke, one breath. You cannot go back over a line.
In ten short steps you will build a one-page site for a calligraphy studio: navigation, a hero, course cards, a booking form, an FAQ and a dark mode. Most of it is plain HTML. Sumi does the styling.
Sumi is a single CSS file with no JavaScript and no build step. Download sumi.min.css, put it next to your HTML file, and link it in the <head>.
Create a file called index.html with this starter:
Open the file in your browser. You should see a centered page with a bold heading, comfortable line spacing and the right colors for your system's light or dark mode.
Two lines matter. The viewport meta tag makes the layout work on phones. The lang attribute lets the browser hyphenate long-form text correctly, so set it to your page's language.
Use sumi.css instead of the minified file while you learn. It has comments that explain every section.
Sumi centers three elements automatically when they are direct children of <body>: <header>, <main> and <footer>. They get a maximum width of 72rem, side padding that adapts to the screen, and sensible vertical spacing. You don't add any classes.
Three classes cover the other layout cases:
| Class | Use it when |
|---|---|
.container | You want the same centered width on another element, such as a <div> or a <section>. |
.container-fluid | You want full width with side padding only. |
.narrow | You want a reading column of 44rem, for a blog post or a sign-in form. |
Headings, paragraphs, lists, quotes and code are styled from the element alone. Headings follow a fluid scale, so an <h1> is about 33px on a phone and 45px on a wide screen with no media queries on your side.
Three patterns make a hero section look finished:
<hgroup> groups a heading with an eyebrow (<h6>) above it and a subtitle (<p>) below it..lead makes an intro paragraph larger..display makes a headline larger still, for a landing page.Small evening classes for complete beginners. Ink, brushes and paper are provided.
For an article or a blog post, wrap the text in .prose. It limits lines to about 68 characters, increases the line spacing slightly and turns on hyphenation. Add .serif to switch to a serif typeface, which many readers find calmer for long texts.
Bottled ink is convenient, but grinding a stick against the stone is part of the lesson. It takes about five minutes, and it slows you down before the first stroke.
One stroke, one breath. You cannot go back over a line.
An <article> normally becomes a card (step 6). With .prose it stays plain text, because a long article should not sit in a box.
.grid places its children in as many columns as fit, each at least 14rem wide. On a phone they stack into one column. You don't set any breakpoints.
Eight students per class, so the teacher sees every stroke.
Brushes, inksticks, stones and paper are ready on your desk.
Every student leaves with a finished, signed piece.
When you need exact columns, add .cols-2, .cols-3, .cols-4, .cols-6 or .cols-12. These apply from 48rem (768px) upwards; below that the grid is a single column. In a 12-column grid, children can take .span-2 to .span-9, or .span-full for a whole row.
For a single row of items, use .row (a wrapping flex row with a gap). Add .spread to push items to both ends. .stack is the vertical version.
Every <article> is a card. If the article starts with a <header> or ends with a <footer>, those become shaded bands across its full width. Put cards in a grid and they line up.
One two-hour session. Try the brush, the ink and the basic strokes.
Weekly classes from first strokes to a finished hanging scroll.
One hour with a teacher, at a time that suits you.
.full stretches a button across its container. Buttons come in .secondary and .contrast colors, an .outline style, and .small and .large sizes. You can combine them, as in class="secondary outline small".
Form fields need no classes. Wrap each field in its <label> and it gets full width, padding and a focus ring. A <small> placed right after a field becomes helper text.
Try submitting the empty form. Fields turn red because a small script sets aria-invalid="true" on them. Sumi styles that attribute, and screen readers announce it too. Valid fields get aria-invalid="false" and turn green.
Two more patterns are worth knowing:
<input type="checkbox" role="switch"> draws a toggle switch instead of a checkbox.<div role="group"> joins an input and a button into one control, like a search bar.Sumi styles the browser's own interactive elements, so most of these work with no JavaScript.
Each <details> opens and closes on click. Add open to start one expanded.
No. The taster evening and the six-week course both start from the very first stroke.
Something you don't mind getting ink on. Sumi ink does not wash out of cotton.
Add class="dropdown" to a <details> and put a list inside. Add .right as well to align the menu to the right edge.
Put an <article> inside a <dialog>. Opening a modal needs one line of JavaScript, dialog.showModal(). Closing does not: a button inside <form method="dialog"> closes it, and so does the Esc key.
Three attributes and one class cover the remaining feedback you'll need.
data-tooltip="…" shows a tooltip on hover and keyboard focus. Add data-placement="bottom" to show it below.aria-busy="true" adds a spinner to any element. Remove the attribute when loading finishes..callout is a message box. Add .success, .warning or .danger for the color.Every color, font, size and radius in Sumi is a CSS custom property that starts with --s-. To rebrand the whole site, put your own values in a stylesheet that loads after Sumi:
Custom properties also work on a single element. These buttons use the same classes as before. Only the parent's style attribute changes them:
Sumi uses each system's own interface font by default, so pages load with no font downloads. To use a web font, load it as usual and point a token at it. --s-font-body sets the text, and --s-font-heading sets the headings (it follows the body font unless you set it).
Dark mode is already on. Sumi follows the visitor's system setting. To force a mode, set data-theme on the <html> element:
A theme switch is a few lines of JavaScript. This one remembers the visitor's choice:
data-theme also works on any element, so one section can be dark inside a light page:
Thursdays from 19:00 to 21:00, by candlelight.
The dark palette is defined twice in sumi.css: once for the system setting and once for data-theme="dark". If you change dark colors, change both blocks.
Most of Sumi's element styles sit inside :where(), which gives them zero specificity. Any selector you write wins, without !important and without long selector chains.
Build new components from Sumi's tokens, as the badge above does. They then follow dark mode and your brand colors automatically. The tokens you'll use most are --s-text, --s-muted, --s-bg, --s-surface, --s-border, --s-primary and --s-radius.
To turn off a default for one element, override it with a class. For example, an <article> that should not be a card:
Here are the pieces put together into one page. The full source is under the preview, ready to copy into index.html.
Small evening classes for complete beginners. Ink, brushes and paper are provided.
One two-hour session to try the basic strokes.
From first strokes to a finished hanging scroll.
One hour with a teacher, when it suits you.
No. Every course starts from the very first stroke.
Something you don't mind getting ink on.
sumi.css. Each section explains what it styles and which tokens it uses.--s-primary and --s-font-heading first. Those two tokens change the character of a site the most.Every class and attribute Sumi responds to. Everything else is plain HTML.
| Class or attribute | Goes on | Effect |
|---|---|---|
.container .container-fluid .narrow | any block | Centered page width, full width, reading width |
.grid | wrapper | Auto-fitting columns, at least --s-grid-min wide |
.cols-2 … .cols-12 | .grid | Fixed columns from 48rem up |
.span-2 … .span-9, .span-full | grid child | Spans several columns |
.row .spread .stack | wrapper | Flex row, row with space between, vertical stack |
.display .lead | heading, paragraph | Hero headline, intro paragraph |
.prose .serif | text wrapper | Long-form reading column, serif typeface |
.secondary .contrast .outline | button | Button colors and outline style |
.small .large .full | button | Button sizes, full width |
role="button" | <a> | Link that looks like a button |
role="group" | wrapper | Joins buttons or input + button |
role="switch" | checkbox | Toggle switch |
aria-invalid | field | Red when true, green when false |
aria-busy="true" | any element | Loading spinner |
aria-current="page" | nav link | Highlights the current page |
data-tooltip data-placement | any element | Tooltip, above or below |
data-theme | <html> or any element | Forces light or dark |
.dropdown .right | <details> | Dropdown menu, right-aligned |
.callout + .success .warning .danger | block | Message box |
.striped | <table> | Zebra rows |
.muted .small .center .sr-only .overflow-auto | any element | Utilities |
Sumi works in current versions of Chrome, Edge, Firefox and Safari. It relies on :where(), :has() and color-mix(), which are available from Chrome and Edge 111, Firefox 121 and Safari 16.2. Older browsers still show readable pages, with plainer hover and focus colors.