v0.4.0 · MIT · pnpm add @toybreaker/fiko

FIKO

A modern CSS framework built on cascade layers,
OKLCH colors, and design tokens.
Zero specificity wars. Smiling DX.

cascade layers oklch colors design tokens container queries zero js

Get started in seconds

Available on npm as @toybreaker/fiko

shell
pnpm add @toybreaker/fiko
css
@import "fiko";

/* then layer your brand tokens on top */
@import "./000/palette.css"   layer(tokens);
@import "./000/roles.css"     layer(theme);
@import "./000/overrides.css" layer(theme);

Or use directly from a CDN — no build step required:

html
<link rel="stylesheet" href="https://unpkg.com/fiko/index.css" />

6 cascade layers

reset → tokens → theme → layout → components → utilities. Crystal-clear specificity order.

OKLCH colors

Perceptually uniform color space. Override one token, the whole palette adapts.

Container queries

Layout responds to its container, not the viewport. Truly composable components.

Design tokens first

Every visual decision lives in CSS custom properties. Override in your brand layer.

CSS custom properties

Colors

--brand
oklch(0.68 0.26 38)
--brand_lighter
oklch(l+0.10 c h)
--brand_darker
oklch(l-0.20 c h)
--brandT25
color-mix 75% transparent
--brandT50
color-mix 50% transparent
--cta
= --brand
--brand_invert
oklch(l c h+180)
--dark
oklch(0.18 0.01 265)
--light
oklch(0.97 0.005 74)
--surface
= --light
--gradient_o
radial brand
--gradient_h
linear →
--gradient_v
linear ↓
--gradient_warm
linear warm

Spacing

--spaceH
1.75svw — horizontal rhythm
--spaceV
1.5svh — vertical rhythm
--space
calc(spaceH + spaceV / 2)
--breath
calc(space * 1.5) — gutters
--radius
6px
--borderpx
3px
--tap_size
clamp(42px, 5vw, 52px)

Typography

Display h1 .h1 — font-size * 2.5, weight 500
Heading h2 .h2 — font-size * 2.0, weight 500
Heading h3 .h3 — font-size * 1.75, weight 400
Subheading h4 .h4 — font-size * 1.5, weight 400
Label h5 .h5 — font-size * 1.25, weight 400
Body text — --font_size: clamp(1.1rem, 0.34vw + 0.92rem, 1.21rem) base — weight 500

Font weight scale

100 ultralight 200 300 400 thin (default body) 500 light alias 600 regular alias 700 medium alias 800 bold alias

Breakpoints

--bp_xs390px
--bp_sm576px
--bp_md768px
--bp_lg992px
--bp_xl1200px
--max_window_width1400px

Interactive elements

Buttons

button, .button, .button_cta
html
<button>Default button</button>
<a class="button button_cta"><button>CTA button</button></a>
<a class="button"><button>Link button</button></a>
<button data-state="disabled">Disabled state</button>

Heading size classes

.h1–.h6 are size modifiers — apply to any element

.h1

FIKO Look good or die!

.h2

FIKO Look good is easy now!

.h3

FIKO Looking good is one install away

.h4

FIKO Heading to looking good? Easy! Plug in FIKO dude!

.h5

FIKO Look good, but don't die trying, actually!

.h6

HEY FLOWER, HERE, HAS SOME FIKO... LOOK GOOD, MANY BEES COMING!

html
<p class="h1">Paragraph at .h1 size</p>
<p class="h2">Paragraph at .h2 size</p>
<p class="h3">Paragraph at .h3 size</p>
<div class="h4">A div at .h4 size</div>
<p class="h5">Paragraph at .h5 size</p>
<p class="h6">Paragraph at .h6 size</p>

Decouple semantics from appearance

the HTML heading hierarchy stays intact — only the visuals change

Promote a non-heading to h2 weight — the h1 in the DOM is untouched:

Page title (plain h1, no class)

Promo callout — a div at h2 visual weight

Three consecutive headings, same visual weight — e.g. a hero with multiple big lines:

First big line

Second big line

Third big line

html
<!-- non-heading at h2 visual weight, h1 cascade untouched -->
<h1>Page title</h1>
<div class="h2">Promo callout</div>

<!-- three consecutive headings, same visual size -->
<h2 class="h1">First big line</h2>
<h3 class="h1 uppercase">Second big line</h3>
<h4 class="h1">Third big line</h4>

Text utilities

.dim, .underline, .typewriter, .mono, .prose, .uppercase, .cat

.dim

Published 3 days ago · 4 min read

.underline

Read the full documentation →

.typewriter

Dear Claude, it all started with a CSS reset…

.mono

pnpm add @toybreaker/fiko

.prose

Good design is as little design as possible. Comfortable reading wraps at 66 characters, for visual comfort on long paragraphs.

.uppercase

new release

.cat

the quick brown fox jumps over the lazy cat

html
<p class="dim">Dimmed text</p>
<a class="underline" href="#">Underlined link</a>
<p class="typewriter">Typewriter font</p>
<p class="mono">Mono sans-serif system font</p>
<p class="prose">Prose constrained to 66ch</p>
<p class="uppercase">Uppercase text</p>
<p class="cat">capitalize text</p>

Dot leader

.dots
Menu item 42
Table of contents 7
html
<div style="display: flex; align-items: baseline;">
  <span>Menu item</span>
  <span class="dots"></span>
  <span>42</span>
</div>

Sticky header / controls

.controls, .controls.sticky
Nav bar — fixed height var(--sticky_header_height)
html
<header class="controls sticky">
  <!-- nav content -->
</header>

Data states

data-state="loading | disabled | empty"
loading (cursor: progress, opacity 0.7)
disabled (opacity 0.5, no pointer)
this is hidden visible — sibling with data-state="empty" is hidden
html
<div data-state="loading">loading...</div>
<div data-state="disabled">disabled</div>
<span data-state="empty">hidden</span>

Blockquote

blockquote, cite
Cascade layers are the single biggest shift in CSS architecture in a decade. A happy fiko user
html
<blockquote>
  Cascade layers are the biggest shift in CSS architecture in a decade.
  <cite>A happy fiko user</cite>
</blockquote>

Accordion

details, summary — zero JS, semantic HTML
Default accordion — icon: + / ×
Cascade layers are the single biggest shift in CSS architecture in a decade. Override in order, no specificity wars.
Open by default
Add the open attribute to start expanded. The close icon is × by default.
Custom icon — ↓ / ↑
Override --accordion_icon_closed and --accordion_icon_open per element or globally in your brand layer.
Custom icon — ▶ / ▼
Any character works — emoji, arrows, symbols. Set the tokens once on a wrapper to apply to all children.
html
<!-- default icons -->
<details>
  <summary>Title</summary>
  <div>Content goes here.</div>
</details>

<!-- custom icons via CSS token -->
<details style="--accordion_icon_closed: '↓'; --accordion_icon_open: '↑';">
  <summary>Custom icon</summary>
  <div>Content goes here.</div>
</details>

<!-- set icons for a whole group -->
<div style="--accordion_icon_closed: '▶'; --accordion_icon_open: '▼';">
  <details><summary>Item 1</summary><div>…</div></details>
  <details><summary>Item 2</summary><div>…</div></details>
</div>

Layout primitives

Container

.container — max-width + container query context
.container — width: min(100% - breath*2, --container_xl)
Sets up container query context for responsive children.
html
<div class="container">
  <!-- max-width centered, container query context -->
</div>

Responsive grid

.grid — 1 → 2 → 3 → 4 cols via container queries
Cell 1
Cell 2
Cell 3
Cell 4
html
<div class="container">
  <div class="grid">
    <div>Cell 1</div>
    <div>Cell 2</div>
    <div>Cell 3</div>
    <div>Cell 4</div>
  </div>
</div>

Bento box

.bento — 12-col mosaic, dense auto-flow, span modifiers
Featured — 2×2
Card 2
Card 3
Card 4
Card 5
html
<!-- default: --bento_cols:3 --bento_rows:3 -->
<div class="bento">
  <div>Featured</div>   <!-- :first-child → 2 cols × 2 rows -->
  <div>Card 2</div>     <!-- fills 3×3 grid perfectly -->
  <div>Card 3</div>
  <div>Card 4</div>
  <div>Card 5</div>
</div>

<!-- change layout via tokens: -->
<div class="bento" style="--bento_cols:4; --bento_rows:2">…</div>
<div class="bento" style="--bento_cols:2; --bento_rows:4">…</div>

<!-- child modifiers: .bento-col-2 .bento-row-2 .bento-full -->

Navigation

nav — flex, space-between, wraps on mobile
html
<nav>
  <span>brand</span>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

Utility classes

Padding blocks

.pad_block_sm/md/lg/xl/xxl
.pad_block_sm — spaceV * 1.5
.pad_block_md — spaceV * 3
.pad_block_lg — spaceV * 6
html
<section class="pad_block_sm">...</section>
<section class="pad_block_md">...</section>
<section class="pad_block_lg">...</section>
<section class="pad_block_xl">...</section>
<section class="pad_block_xxl">...</section>

Spacer blocks

.spacer_block_xs/sm/md/lg/xl/xxl
.spacer_block_xs — 1svh max 88px
.spacer_block_sm — 2.5svh max 100px
.spacer_block_md — 5svh max 133px
html
<div class="spacer_block_xs"></div>
<div class="spacer_block_sm"></div>
<div class="spacer_block_md"></div>
<div class="spacer_block_lg"></div>

Gap scale

.gap_sm/md/lg
item 1
item 2
.gap_sm
item 1
item 2
.gap_md
html
<div class="flex gap_sm">...</div>
<div class="flex gap_md">...</div>
<div class="flex gap_lg">...</div>

Text utilities

.center, .uppercase, .capitalize, .strikethrough, .pretty

.center — text-align: center

.uppercase text

.capitalize text

.strikethrough text

.pretty — text-wrap: pretty

html
<p class="center">centered</p>
<p class="uppercase">uppercase</p>
<p class="capitalize">capitalize</p>
<p class="strikethrough">strikethrough</p>
<p class="pretty">text-wrap: pretty</p>

Visibility

.hide, .maybe_hide, .sr_only
.hide → not visiblehidden .maybe_hide: visible ≥480px Screen-reader only .sr_only in DOM, invisible
html
<span class="hide">always hidden</span>
<span class="maybe_hide">hidden below 480px</span>
<span class="sr_only">screen-reader only</span>

Gradient utilities

.gradient_o, .gradient_h, .gradient_v, .gradient_warm
.gradient_o
.gradient_h
.gradient_v
.gradient_warm
html
<div class="gradient_o">...</div>
<div class="gradient_h">...</div>
<div class="gradient_v">...</div>
<div class="gradient_warm">...</div>

Maximise bleed

.maximise — negative container margin bleed
.maximise — bleeds past container padding
html
<div class="maximise gradient_o">
  full-bleed content inside a .container
</div>