nol.satu nol.satu design system v2.0
nol.satu design system · v2.0 · 30 September 2026

Clean bones. Colorful parts.

nol.satu is a builder for makers: every site and page people make is assembled from the same small blocks. This system keeps the frame quiet and precise, so the blocks, and the people arranging them, get the colour.

actionideastructuregrowthflowwarmth
/* Tailwind v4, inside the nol.satu monorepo */
@import "@nolsatu/design-system/tailwind.css";

What changed from v1.2

v1.2 set the personality. v2 makes it sharper, fills the gaps a real product needs, and ships as code.

Area
v1.2
v2.0
Frame
Ink + cream, light only
Stone neutrals, a cleaner descendant of cream. Light and dark are equal.
Brand colour
Six equal accents
Teal forest leads every action. Berry supports it and lives in the mark.
Categories
Pastel/saturated pairs; lime, gold and pink unreadable as text
Six hues matched in OKLCH. Structure moves to cobalt, Warmth to orchid. All pass AA.
Mark
Filled capsules, 3 PNGs
Monoline redraw on a 56-unit grid, 29 SVG files including lockups, favicon and app icons.
Type
Space Grotesk + Inter + JetBrains Mono
Space Grotesk + Instrument Sans + IBM Plex Mono, with a fluid scale.
Radius
8 · 14 · 22
4 · 6 · 12 · 18 · 24 · full, each assigned to a job.
New
None
Status colours, elevation, motion, focus, z-index, control sizes, contrast report.
Delivery
PDF
tokens.json → CSS variables + Tailwind v4, DESIGN.md, this site, PDF.
Brand

Built by two people, for people building things.

Two people, one studio table.

nol.satu started the way a lot of small businesses do, with two founders, one laptop and more ideas than time. Mutia took the parts that needed patience. Alshaadiq took the parts that needed nerve.

Every feature still has to pass the day-one test. Would this have made things easier for two people just starting out?

Founded by
Mutia & Alshaadiq
Started as
A tool for their own studio
First customer
Each other
Now building for
Anyone starting out

The mark: two forms, one overlap

Two capsules lean toward each other like two people mid-conversation. Where they cross, a third shape appears. That shape is the company, and it only exists because both showed up. In v2 the forms are drawn as lines, so the overlap is the only solid part of the mark.

56 × 56 grid 16u apart lean 8° 24 × 44 r 12 · stroke 4.5
Left form · berryThe first founder. Leans right, toward the other.
Right form · tealThe second founder, and the colour that leads the product.
The overlapThe only filled shape. Deep teal on light backgrounds, near-white on dark, so it always reads as the brightest or darkest thing in the mark.
Soft, rounded endsCapsules instead of hard geometry: approachable over corporate.
Small sizesAt 24px and below, use mark-small. It has a 6-unit stroke so the lines survive a favicon.

Logo files

Every file is an outlined SVG, so it needs no fonts. Each lockup comes in five colourways: colour (-), -dark, -mono (currentColor), -ink and -white. On the live site, the file names link straight to the SVGs.

Clear space. Keep half the mark's width free on every side of a lockup.
Don't rotate or straighten the forms. The lean is the idea.
Don't put the colour mark on teal. Use -white on brand fills and photos.
Don't recolour the forms. It's berry and teal, or one colour.

Minimum sizes: mark 16px (use mark-small up to 24px); horizontal lockup 88px wide. Always write the name in lowercase with its dot: nol.satu, even at the start of a sentence.

Foundations · Colour

A quiet stone frame, one teal voice.

Neutrals do the structural work. Teal marks everything you can act on. Berry is rare on purpose. Category colours belong to blocks, never to UI chrome. In code, always use semantic tokens (bg-surface, text-fg-muted). Palette steps are for illustration and the mark.

Palette

Semantic tokens

These switch automatically between light and dark. The Tailwind class is the token name: color.surface.subtle becomes bg-surface-subtle, and color.fg.muted becomes text-fg-muted.

TokenTailwindLightDarkUse for

Status

Status colours report what happened. They never decorate. Pair each one with an icon or a word, so colour is never the only signal.

Light
LiveDomain pendingPublish failedScheduledDraft
Dark
LiveDomain pendingPublish failedScheduledDraft

Block categories

Every block belongs to one of six categories. Its colour appears as a tag, a dot, the drag handle and the canvas outline while it's being placed. The six hues are generated at matching lightness and chroma in OKLCH, so none shouts over the others and every tag passes 7:1.

Contrast report

The build checks every pairing below in both themes and fails if one drops under its minimum: 4.5:1 for text, 3:1 for control boundaries, focus rings and dots.

ForegroundBackgroundMinLightDark
Foundations · Typography

A geometric voice, a precise workhorse.

Space Grotesk carries the personality in headings and the wordmark. Instrument Sans is sharper and more compact than Inter, so dense builder UI stays crisp. IBM Plex Mono marks anything structural: labels, tags, URLs and code.

Display · 600–700AaSpace GroteskHeadings, the wordmark, numbers that matter. Never below 16px.
Body · 400–600AaInstrument SansEverything people read and click. 15px is the default in the builder.
Utility · 400–500AaIBM Plex MonoUppercase labels, category tags, URLs, keyboard shortcuts, code.

Sizes marked fluid scale from a 400px viewport up to 1280px. Use sentence case everywhere. Use tabular numbers in tables and counters. Keep running text under 66 characters wide.

Foundations · Scale

Space, radius, elevation, motion.

Everything sits on a 4px grid. In Tailwind, --spacing is 4px, so p-4 is 16px. Corners get softer as objects get bigger.

ControlHeightUse
control-sm28pxToolbars, table rows, dense panels
control-md34pxDefault buttons and inputs
control-lg42pxMarketing, onboarding, empty states

Elevation

Most surfaces are flat and use a 1px line. Shadows are for things that float above the page. In dark mode, shadows get a faint light edge so they still read.

shadow-xsButtons, inputs
shadow-smSelected block, raised card
shadow-mdMenus, popovers, dragged block
shadow-lgModals, command palette

Motion

Motion confirms what happened. It never performs. Most things take 120–180ms. snap is reserved for a block landing in place after a drag. With reduced motion on, every duration drops to 0.

standard · 180ms
enter · 280ms
exit · 120ms
snap · 420ms
Durations: instant 0 · fast 120 · base 180 · slow 280 · slower 420ms
Build · Components

Built from the same small parts.

Each component shows its Tailwind v4 recipe, using the utilities from tokens/tailwind.css. Use the theme toggle at the top to check every one in both themes.

Buttons

One primary button per view. Labels are verbs that say exactly what happens: "Publish site", not "Submit". Secondary is the default for everything else. Ghost is for toolbars.

<button class="inline-flex h-[34px] items-center gap-2 rounded-sm bg-brand px-3.5 text-[13.5px] font-medium text-on-brand shadow-xs hover:bg-brand-hover">Publish site</button>
<button class="inline-flex h-[34px] items-center gap-2 rounded-sm border border-line-control bg-surface px-3.5 text-[13.5px] font-medium text-fg shadow-xs hover:bg-surface-hover">Add block</button>

Form fields

Labels always sit above the field. Hints explain the format before anyone gets it wrong. Errors replace the hint and say how to fix it.

Shown in browser tabs and search results.
Use letters, numbers and hyphens only.
Available on the Studio plan.
<label class="type-body-sm font-medium" for="name">Site name</label>
<input id="name" class="h-[34px] w-full rounded-sm border border-line-control bg-surface px-3 text-fg placeholder:text-fg-subtle focus:border-brand focus:outline-none focus:ring-3 focus:ring-brand/20 aria-invalid:border-danger">
<p class="type-caption text-fg-muted">Shown in browser tabs and search results.</p>

Selection

<input type="checkbox" class="size-[18px] appearance-none rounded-2xs border-[1.5px] border-line-control bg-surface checked:border-brand checked:bg-brand">
<button role="switch" class="h-5 w-9 rounded-full bg-line-strong aria-checked:bg-brand">…</button>

Tags & badges

Tags name a block's category and are always mono and lowercase. Badges report status and always include a word. new uses berry and is the only badge allowed to.

actionideastructuregrowthflowwarmth LiveDraftnew
<span class="inline-flex h-[22px] items-center gap-1.5 rounded-full bg-cat-structure-subtle px-2 type-label normal-case text-cat-structure-fg">
  <span class="size-1.5 rounded-full bg-cat-structure"></span>structure
</span>

Blocks on the canvas

A block is the product's core object. At rest it's a flat card. When selected it gets a 2px teal ring. While dragging it tilts slightly and gets shadow-md, and it lands with the snap easing. The drop target uses brand-subtle with a dashed teal line.

Signup form

action

3 fields · sends replies to mutia@studio.id

Pricing table

structure

Drag to reorder columns · click to edit copy · Connect Stripe

Drop here to add below Pricing table

Testimonials

warmth

4 quotes · carousel

<article class="grid rounded-md border border-line bg-surface p-3.5 shadow-xs data-selected:border-transparent data-selected:ring-2 data-selected:ring-brand data-dragging:-rotate-1 data-dragging:shadow-md transition ease-snap">…</article>

Feedback

Toasts confirm and disappear after 4 seconds. Inline alerts stay until the problem is fixed. Say what happened, then what to do.

Published. Your site is live at mutia-ceramics.nolsatu.id
Your domain is connectingThis usually takes under an hour. We'll email you when it's live.
2 images are over 5 MBThey'll load slowly on phones. Compress them or swap in smaller versions.
Couldn't publishYour domain isn't pointing to nol.satu yet. Check your DNS settings and try again.

Menus, tooltips, modals, tabs

Preview on phone

No pages yet

Start from a template or an empty page. You can change everything later.

Builder shell

The whole system at once, in both themes. The frame stays neutral, category colour appears only on blocks, and teal marks what you can act on.

Voice

Plain, active, never precious.

Write from the maker's side of the screen. Talk about what they're doing, not how the system works underneath.

Name the action, then confirm it

An action keeps its name from click to confirmation: Publish → Publishing… → Published.

Errors say what to do next

What happened, then how to fix it. No apologies, no error codes up front, no "something went wrong".

Sentence case, short sentences

One idea per sentence. Buttons are two or three words. No exclamation marks in the UI.

Specific beats clever

Say "3 fields", "6 blocks", "under an hour". Real numbers and real names build trust faster than personality.

Avoid
Use
"Your webhook configuration has been submitted for processing."
"Saved. Your form now sends replies to your inbox."
"Error: operation failed unexpectedly."
"Couldn't publish. Check your domain settings and try again."
"Are you sure you want to proceed?"
"Delete "About"? This removes the page and its 6 blocks."
"Successfully saved!"
"Saved."
"Invalid input."
"Use letters, numbers and hyphens only."
Build · Tokens & setup

One source, fetchable from anywhere.

tokens/tokens.json (W3C Design Tokens format) is the source of truth. Every other file is generated from it by npm run tokens. All files under /tokens and /assets are served with open CORS, so tools, agents and other repos can fetch them directly.

Tailwind v4 in the builder app

/* apps/<app>/src/app.css — add "@nolsatu/design-system": "*" to the app's dependencies */
@import "tailwindcss";
@import "@nolsatu/design-system/tailwind.css";
/* outside the monorepo: copy https://design.nolsatu.id/tokens/tailwind.css and import that */

/* Now available: bg-canvas bg-surface text-fg text-fg-muted border-line border-line-control
   bg-brand hover:bg-brand-hover text-on-brand bg-cat-flow-subtle text-cat-flow-fg
   rounded-sm|md|lg shadow-xs..lg ease-standard|snap type-h1 type-body type-label  */

Theme switching without a flash

Semantic colours follow the OS by default. To let people choose, set data-theme on <html> before the first paint. Tailwind's dark: variant follows the same attribute.

<script>
  try {
    const t = localStorage.getItem("ns-theme"); // "light" | "dark" | null (system)
    if (t) document.documentElement.dataset.theme = t;
  } catch {}
</script>

Naming

tokens.json pathCSS variableTailwind
color.surface.subtle--ns-color-surface-subtlebg-surface-subtle
color.brand.default--ns-color-brandbg-brand text-brand
color.cat.flow.fg--ns-color-cat-flow-fgtext-cat-flow-fg
typography.h2--ns-text-h2-size …type-h2 or font-display text-h2
radius.md--ns-radius-mdrounded-md
shadow.md--ns-shadow-mdshadow-md
easing.snap--ns-easing-snapease-snap
z.modal--ns-z-modalz-(--ns-z-modal)

Tailwind's default colour, radius, shadow, text-size and easing scales are switched off, so only nol.satu values can be used. The default segment is dropped from names.