Oat CSS
SkillDev toolsOur preferred CSS framework — ultra-lightweight, semantic HTML UI library (~8KB). Style web UIs with semantic HTML and minimal classes. Override with app-level SCSS only when necessary.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Oat CSS skill
What this skill tells your AI
The instructions your AI receives, as published by spinspire/pocketbase-sveltekit-starter in .agents/skills/oat-css/SKILL.md and read by ahel’s review.
Oat is an ultra-lightweight (~8KB min+gz), zero-dependency, semantic HTML/CSS/JS UI library by Kailash Nadh (5k+ stars on GitHub). It styles native HTML elements out of the box — no classes needed for basic UIs. Dynamic components use WebComponents with minimal JS.
Philosophy: Semantic tags and attributes are styled contextually without classes, forcing best practices and reducing markup class pollution. Only reach for custom CSS when Oat's defaults don't cover your use case.
Installation
npm (SvelteKit, Vite, etc.)
bun add @knadh/oat
In your app entry or root SCSS:
@import '@knadh/oat/oat.min.css';
Import the JS for dynamic components (dialog, dropdown, tabs, toast, tooltip, sidebar):
import '@knadh/oat/oat.min.js';
Or selectively import individual files from @knadh/oat/css/ and @knadh/oat/js/.
CDN
<link rel="stylesheet" href="https://unpkg.com/@knadh/oat/oat.min.css">
<script src="https://unpkg.com/@knadh/oat/oat.min.js" defer></script>
Core Principle
Use semantic HTML. Oat styles elements based on their tag and ARIA attributes, not CSS classes.
<!-- ❌ Class-heavy approach you DON'T need with Oat -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Title</h3>
</div>
<div class="card-body">
<p>Content</p>
</div>
<div class="card-footer">
<button class="btn btn-primary">Save</button>
</div>
</div>
<!-- ✅ Semantic HTML — Oat styles this automatically -->
<article class="card">
<header>
<h3>Title</h3>
</header>
<p>Content</p>
<footer>
<button>Save</button>
</footer>
</article>
When to Add Custom CSS/SCSS
Only override when:
- Brand colors — redefine CSS variables in
:root(see Theming below) - Layout — use Oat's
.hstack,.vstack,.container/.row/.col-*grid, or add your own - Complex compositions — recipes like stats cards, split buttons, form cards
- Custom animations or interactions — Oat doesn't ship opinionated transitions beyond the basics
Every Oat component below is purely semantic HTML. No custom CSS needed.
Components
Typography
<h1>Heading 1</h1> <h2>Heading 2</h2> <h3>Heading 3</h3>
<p>Paragraph with <strong>bold</strong>, <em>italic</em>, and <a href="#">a link</a>.</p>
<pre><code>code block</code></pre>
<blockquote>Blockquote</blockquote>
<hr>
<ul><li>List item</li></ul>
<ol><li>Ordered item</li></ol>
Button
<button> is styled by default. Use data-variant for semantics, .outline/.ghost for style, .small/.large for size.
<button>Primary</button>
<button data-variant="secondary">Secondary</button>
<button data-variant="danger">Danger</button>
<button class="outline">Outline</button>
<button class="ghost">Ghost</button>
<button class="small">Small</button>
<button class="large">Large</button>
<button disabled>Disabled</button>
<a href="#" class="button">Link as button</a>
Button group:
<menu class="buttons">
<li><button class="outline">Left</button></li>
<li><button class="outline">Center</button></li>
<li><button class="outline">Right</button></li>
</menu>
Card
<article class="card">
<header>
<h3>Card Title</h3>
<p>Description</p>
</header>
<p>Content here.</p>
<footer class="hstack">
<button class="outline">Cancel</button>
<button>Save</button>
</footer>
</article>
Alert
Use role="alert" with optional data-variant (success, warning, error).
<div role="alert" data-variant="success">
<strong>Success!</strong> Your changes have been saved.
</div>
<div role="alert" data-variant="warning">
<strong>Warning!</strong> Please review before continuing.
</div>
<div role="alert">
<strong>Info</strong> This is a default alert.
</div>
<div role="alert" data-variant="error">
<strong>Error!</strong> Something went wrong.
</div>
Form
Wrap inputs in <label data-field> for proper styling. Input groups use <fieldset class="group">.
<form>
<label data-field>
Name
<input type="text" placeholder="Enter your name" />
</label>
<label data-field>
<input type="checkbox" /> I agree
</label>
<label data-field>
<input type="checkbox" role="switch" checked> Toggle
</label>
<fieldset class="hstack">
<legend>Preference</legend>
<label><input type="radio" name="pref"> A</label>
<label><input type="radio" name="pref"> B</label>
</fieldset>
<fieldset class="group">
<input type="text" placeholder="Search" />
<button>Go</button>
</fieldset>
<div data-field="error">
<label>Email</label>
<input type="email" aria-invalid="true" value="bad" />
<div class="error" role="status">Invalid email.</div>
</div>
</form>
Dialog (modal)
Uses native <dialog> with commandfor/command attributes (zero JS required).
<button commandfor="my-dialog" command="show-modal">Open</button>
<dialog id="my-dialog" closedby="any">
<form method="dialog">
<header><h3>Title</h3></header>
<div><p>Content</p></div>
<footer>
<button commandfor="my-dialog" command="close" class="outline">Cancel</button>
<button value="confirm">Confirm</button>
</footer>
</form>
</dialog>
Dropdown
Uses <ot-dropdown> WebComponent + native Popover API.
<ot-dropdown>
<button popovertarget="menu" class="outline">Options ▾</button>
<menu popover id="menu">
<button role="menuitem">Profile</button>
<button role="menuitem">Settings</button>
<hr>
<button role="menuitem">Logout</button>
</menu>
</ot-dropdown>
Tabs
Uses <ot-tabs> WebComponent.
<ot-tabs>
<div role="tablist">
<button role="tab">Account</button>
<button role="tab">Password</button>
</div>
<div role="tabpanel"><h3>Account Settings</h3></div>
<div role="tabpanel"><h3>Password Settings</h3></div>
</ot-tabs>
Table
<div class="table">
<table>
<thead><tr><th>Name</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Alice</td><td><span class="badge" data-variant="success">Active</span></td></tr>
</tbody>
</table>
</div>
Badge
<span class="badge">Default</span>
<span class="badge" data-variant="success">Success</span>
<span class="badge" data-variant="danger">Danger</span>
<span class="badge" data-variant="warning">Warning</span>
<span class="badge outline">Outline</span>
Accordion
Native <details>/<summary>.
<details>
<summary>What is Oat?</summary>
<p>Oat is a minimal, semantic-first UI library.</p>
</details>
<details name="group">
<summary>Grouped</summary>
</details>
Progress & Meter
<progress value="60" max="100"></progress>
<meter value="0.8" min="0" max="1" low="0.3" high="0.7" optimum="1"></meter>
Spinner
<div aria-busy="true"></div>
<div aria-busy="true" data-spinner="large"></div>
<button aria-busy="true" disabled>Loading</button>
<div aria-busy="true" data-spinner="large overlay">Content dims</div>
Skeleton
<div role="status" class="skeleton line"></div>
<div role="status" class="skeleton box"></div>
Avatar
<figure data-variant="avatar" aria-label="Jane Doe">
<img src="/avatar.svg" alt="" />
</figure>
<figure data-variant="avatar" aria-label="Oat">
<abbr title="Jane Doe">OT</abbr>
</figure>
Sidebar
<div data-sidebar-layout>
<aside data-sidebar>
<nav><ul><li><a href="#" aria-current="page">Home</a></li></ul></nav>
</aside>
<main>Content</main>
</div>
<!-- With top nav -->
<body data-sidebar-layout>
<nav data-topnav>
<button data-sidebar-toggle aria-label="Menu">☰</button>
<span>App</span>
</nav>
<aside data-sidebar>...</aside>
<main>...</main>
</body>
Toast
ot.toast('Saved!', 'Success', { variant: 'success' })
ot.toast('Error!', 'Oops', { variant: 'danger', placement: 'top-left' })
ot.toast('Warning', null, { variant: 'warning' })
ot.toast('Info', null, { placement: 'top-center' })
// Custom HTML
ot.toast.el(document.querySelector('#my-toast-template'), { duration: 8000 })
// Clear all
ot.toast.clear()
Tooltip
Just use the title attribute.
<button title="Save your changes">Save</button>
<button title="On left" data-tooltip-placement="left">Left</button>
<button title="On bottom" data-tooltip-placement="bottom">Bottom</button>
Grid
12-column CSS grid.
<div class="container">
<div class="row">
<div class="col-4">4 cols</div>
<div class="col-4">4 cols</div>
<div class="col-4">4 cols</div>
</div>
<div class="row">
<div class="col-6">6 cols</div>
<div class="col-4 offset-2">4 cols offset 2</div>
</div>
</div>
Utilities
Common utility classes from utilities.css:
| Class | Purpose |
|---|---|
.hstack | Horizontal flex container |
.vstack | Vertical flex container |
.justify-start/center/end/between | Flex justify |
.items-start/center/end | Flex align |
.gap-{0-8} | Gap spacing |
.mt-{0-8}, .mb-{0-8}, .mx-{0-8}, .my-{0-8} | Margin |
.pt-{0-8}, .pb-{0-8}, .px-{0-8}, .py-{0-8} | Padding |
.text-light | Muted text |
.text-center | Center text |
.align-center | Center content |
.unstyled | Remove list/button default styles |
.table | Scrollable table wrapper |
.badge | Badge/tag |
.button | Link styled as button |
.skeleton | Loading placeholder |
.outline | Outline variant for buttons |
.ghost | Ghost variant for buttons |
.small, .large | Size modifier |
Theming
If asked to customize the default oat-css theme, first offer to replicate one of the themes from https://oat.ink/demo/
If the user still wants to create a new theme, then override ONLY (minimal) what needs to be overridden in ":root".
Oat uses light-dark() for automatic dark mode based on system preference.
See the full list at theme.css.
Dark mode
Set data-theme="dark" on <body> to force dark. Oat also auto-detects prefers-color-scheme.
Design tokens
Oat exposes spacing, typography, shadow, and radius tokens:
.custom {
padding: var(--space-4);
margin-block-end: var(--space-6);
border-radius: var(--radius-medium);
box-shadow: var(--shadow-medium);
transition: transform var(--transition-fast);
}
Community Usage & Extensions
Oat is created by Kailash Nadh and used in his own projects. The community has built:
- oat-chips — Chip/tag component with filters, colors, selection
- oat-animate — Lightweight animation extension with
in-view,hover,on-loadtriggers - oat-table — Sort, filter, and select rows in semantic
<table>
Oat is compared alongside Water.css, Pico CSS, and MVP.css as a minimal, classless, or semantic-first CSS framework. Its distinctive traits are the shadcn-inspired aesthetic, WebComponents for dynamic widgets (tabs, dropdown), and the sub-10KB bundle.
Icons
Oat doesn't bundle an icon set. The approach that fits Oat's philosophy (zero dependencies, vanilla HTML, no build step) is the SVG sprite pattern: one .svg file referenced via <use> in your HTML.
<svg width="24" height="24" fill="currentColor">
<use href="bootstrap-icons.svg#icon-name"/>
</svg>
Style with CSS — size via width/height, color via color/fill.
Recommended: Bootstrap Icons
Lightest sprite (578 KB raw, ~96 KB gzipped), 2,000+ icons, clean design that matches Oat's aesthetic, MIT license.
| Method | Link |
|---|---|
| CDN | https://unpkg.com/bootstrap-icons@1.11.3/bootstrap-icons.svg |
| npm | npm install bootstrap-icons, copy node_modules/bootstrap-icons/bootstrap-icons.svg |
<svg width="16" height="16" fill="currentColor">
<use href="bootstrap-icons.svg#check-circle"/>
</svg>
<svg width="24" height="24" fill="currentColor">
<use href="bootstrap-icons.svg#gear"/>
</svg>
<svg width="20" height="20" fill="currentColor">
<use href="bootstrap-icons.svg#x-lg"/>
</svg>
Browse icons at https://icons.getbootstrap.com — the icon name is the filename without .svg.
Alternative: Tabler Icons
6,128 icons, slightly heavier sprite (2 MB raw, ~700 KB gzipped), more variety and a modern 2px-stroke style. MIT license.
<svg width="24" height="24">
<use href="tabler-sprite.svg#tabler-refresh"/>
</svg>
Tradeoffs
- SVG sprite — one HTTP request, cached after first visit, no JS, no build step. The full set is always loaded regardless of which icons you use (not tree-shakeable).
- Tree-shakeable npm packages (Lucide, Boxicons, Heroicons) require a bundler and a framework — they don't fit Oat's vanilla HTML + CSS philosophy. Use them only if you're already committed to a JS build pipeline.
- Custom sprite — for maximum minimalism, use a CLI like
svg-spriteto build a sprite with only the icons you need. Adds a build step but minimizes payload.
Icon button helper
Oat's .icon utility sizes a button for icon-only usage:
<button class="ghost icon" aria-label="Close">
<svg width="16" height="16" fill="currentColor">
<use href="bootstrap-icons.svg#x-lg"/>
</svg>
</button>
Real-world patterns
These compositions use only Oat's built-in components and utility classes — no custom CSS:
Stats cards (dashboard metrics):
<div class="container">
<div class="row">
<article class="card col-4">
<header class="hstack justify-between items-center">
<h4>Revenue</h4>
<span class="badge" data-variant="success">+12%</span>
</header>
<h2>$42,200</h2>
<progress value="72" max="100"></progress>
</article>
</div>
</div>
Form card:
<article class="card">
<header><h3>Profile</h3><p class="text-light">Update info</p></header>
<label data-field>Name <input type="text"></label>
<label data-field><input type="checkbox" role="switch"> Notifications</label>
<footer class="hstack justify-end mt-4">
<button class="outline">Cancel</button>
<button>Save</button>
</footer>
</article>
Empty state:
<article class="card align-center">
<h3>Nothing here yet</h3>
<p class="text-light">Create something to get started.</p>
<footer class="hstack justify-center mt-4">
<button>New item</button>
</footer>
</article>
Key Guidelines
- Reach for semantic HTML first — a
<button>is already styled, a<dialog>already works - Use
data-variant(not custom classes) for color semantics:primary,secondary,success,danger,warning - Use ARIA attributes (
role="alert",aria-busy="true",role="switch") — Oat styles them automatically - Prefer Oat's composable components (card + hstack + badge + progress) over writing custom SCSS
- Override CSS variables for theming instead of writing component-level overrides
- Keep HTML clean — minimal classes, semantic tags, readable structure
- Only write custom SCSS when Oat doesn't provide a component, or your layout needs unique structure not covered by
.hstack/.vstack/.grid
Signals
- GitHub stars
- 507
- Forks
- 76
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
oat-css- Source
- github.com/spinspire/pocketbase-sveltekit-starter