Oat CSS

SkillDev tools

Our 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.

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:

  1. Brand colors — redefine CSS variables in :root (see Theming below)
  2. Layout — use Oat's .hstack, .vstack, .container/.row/.col-* grid, or add your own
  3. Complex compositions — recipes like stats cards, split buttons, form cards
  4. 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:

ClassPurpose
.hstackHorizontal flex container
.vstackVertical flex container
.justify-start/center/end/betweenFlex justify
.items-start/center/endFlex 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-lightMuted text
.text-centerCenter text
.align-centerCenter content
.unstyledRemove list/button default styles
.tableScrollable table wrapper
.badgeBadge/tag
.buttonLink styled as button
.skeletonLoading placeholder
.outlineOutline variant for buttons
.ghostGhost variant for buttons
.small, .largeSize 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-load triggers
  • 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.

MethodLink
CDNhttps://unpkg.com/bootstrap-icons@1.11.3/bootstrap-icons.svg
npmnpm 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-sprite to 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

  1. Reach for semantic HTML first — a <button> is already styled, a <dialog> already works
  2. Use data-variant (not custom classes) for color semantics: primary, secondary, success, danger, warning
  3. Use ARIA attributes (role="alert", aria-busy="true", role="switch") — Oat styles them automatically
  4. Prefer Oat's composable components (card + hstack + badge + progress) over writing custom SCSS
  5. Override CSS variables for theming instead of writing component-level overrides
  6. Keep HTML clean — minimal classes, semantic tags, readable structure
  7. 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