Components

Oat is an ultra-lightweight HTML + CSS + minimal JS, semantic UI component library with zero dependencies. No framework or build or dev dependencies of any kind. Just include the tiny CSS and JS bundles.

Semantic tags and attributes are styled contextually out of the box without classes, thereby forcing best practices. A few dynamic components are WebComponents.


# Typography

Base text elements are styled automatically. No classes needed.

<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6>

<p>This is a paragraph with <strong>bold text</strong>, <em>italic text</em>, and <a href="#">a link</a>.</p>

<p>Here's some <code>inline code</code> and a code block:</p>

<pre><code>function hello() {
  console.log('Hello, World!');
}</code></pre>

<blockquote>
  This is a blockquote. It's styled automatically.
</blockquote>

<hr>

<ul>
  <li>Unordered list item 1</li>
  <li>Unordered list item 2</li>
  <li>Unordered list item 3</li>
</ul>

<ol>
  <li>Ordered list item 1</li>
  <li>Ordered list item 2</li>
  <li>Ordered list item 3</li>
</ol>
Link

# Accordion

Use native <details> and <summary> for collapsible content.

<details>
  <summary>What is Oat?</summary>
  <p>Oat is a minimal, semantic-first UI component library with zero dependencies.</p>
</details>

<details>
  <summary>How do I use it?</summary>
  <p>Include the CSS and JS files, then write semantic HTML. Most elements are styled by default.</p>
</details>

<details>
  <summary>Is it accessible?</summary>
  <p>Yes! It uses semantic HTML and ARIA attributes. Keyboard navigation works out of the box.</p>
</details>
Link

# Alert

Use role="alert" for alert styling. Set data-variant for success, warning, or error.

<div role="alert">
  <strong>Default Alert</strong> - This is a default alert message.
</div>

<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" data-variant="error">
  <strong>Error!</strong> - Something went wrong.
</div>
Link

# Badge

Use .badge class with variant modifiers.

<span class="badge">Default</span>
<span class="badge secondary">Secondary</span>
<span class="badge outline">Outline</span>
<span class="badge success">Success</span>
<span class="badge warning">Warning</span>
<span class="badge danger">Danger</span>
Link

# Button

The <button> element is styled by default. Add classes for variants.

<button>Primary</button>
<button class="secondary">Secondary</button>
<button class="outline">Outline</button>
<button class="ghost">Ghost</button>
<button class="danger">Danger</button>
<button disabled>Disabled</button>

Sizes

Use .small or .large for size variants.

<button class="small">Small</button>
<button>Default</button>
<button class="large">Large</button>
<a href="#button" class="button">Hyperlink</a>

Button group

Wrap buttons in <menu class="buttons"> for connected buttons.

<menu class="buttons">
  <button class="outline">Left</button>
  <button class="outline">Center</button>
  <button class="outline">Right</button>
</menu>
Link

# Card

Use class="card" for a visual box-like card look.

<article class="card">
  <header>
    <h3>Card Title</h3>
    <p>Card description goes here.</p>
  </header>
  <p>This is the card content. It can contain any HTML.</p>
  <footer class="flex gap-2 mt-4">
    <button class="outline">Cancel</button>
    <button>Save</button>
  </footer>
</article>
Link

# Dialog

Fully semantic, zero-Javascript, dynamic dialog with <dialog>. Use commandfor and command="show-modal" attributes on an element to open a target dialog. Focus trapping, z placement, keyboard shortcuts all work out of the box.

<button commandfor="demo-dialog" command="show-modal">Open dialog</button>
<dialog id="demo-dialog">
  <form method="dialog">
    <header>
      <h3>Title</h3>
      <p>This is a dialog description.</p>
    </header>
    <div>
      <p>Dialog content goes here. You can put any HTML inside.</p>
      <p>Click outside or press Escape to close.</p>
    </div>
    <footer>
      <button type="button" commandfor="demo-dialog" command="close" class="outline">Cancel</button>
      <button value="confirm">Confirm</button>
    </footer>
  </form>
</dialog>

With form fields

Forms inside dialogs work naturally. Use command="close" on cancel buttons to close.

<button commandfor="demo-dialog-form" command="show-modal">Open form dialog</button>
<dialog id="demo-dialog-form">
  <form method="dialog">
    <header>
      <h3>Edit form</h3>
    </header>
    <div>
      <label>Name <input name="name" required></label>
      <label>Email <input name="email" type="email"></label>
    </div>
    <footer>
      <button type="button" commandfor="demo-dialog-form" command="close" class="outline">Cancel</button>
      <button value="save">Save</button>
    </footer>
  </form>
</dialog>

Handling return value

Listen to the native close event to get the button value:

const dialog = document.querySelector("#demo-dialog");
dialog.addEventListener('close', (e) => {
  console.log(dialog.returnValue); // "confirm"
});

or use onclose inline:

<dialog id="my-dialog" onclose="console.log(this.returnValue)">
Link

# Form elements

Form elements are styled automatically. Wrap inputs in <label> for proper association.

<form>
  <label data-field>
    Name
    <input type="text" placeholder="Enter your name" />
  </label>

  <label data-field>
    Email
    <input type="email" placeholder="you@example.com" />
  </label>

  <label data-field>
    Password
    <input type="password" placeholder="Password" aria-describedby="password-hint" />
    <small id="password-hint" data-hint>This is a small hint</small>
  </label>

  <div data-field>
    <label>Country</label>
    <select>
      <option value="">Select a country</option>
      <option value="us">United States</option>
      <option value="uk">United Kingdom</option>
      <option value="ca">Canada</option>
    </select>
  </div>

  <label data-field>
    Message
    <textarea placeholder="Your message..."></textarea>
  </label>

  <label data-field>
    Disabled
    <input type="text" placeholder="Disabled" disabled />
  </label>

  <label data-field>
    File
    <input type="file" placeholder="Pick a file..." />
  </label>

  <label data-field>
    Date and time
    <input type="datetime-local" />
  </label>

  <label data-field>
    Date
    <input type="date" />
  </label>

  <label data-field>
    <input type="checkbox" /> I agree to the terms
  </label>

  <fieldset class="hstack">
    <legend>Preference</legend>
    <label><input type="radio" name="pref">OptionA</label>
    <label><input type="radio" name="pref">Option B</label>
    <label><input type="radio" name="pref">Option C</label>
  </fieldset>

  <label data-field>
    Volume
    <input type="range" min="0" max="100" value="50" />
  </label>

  <button type="submit">Submit</button>
</form>

Input group

Use .group on a <fieldset> to combine inputs with buttons or labels.

<fieldset class="group">
  <legend>https://</legend>
  <input type="url" placeholder="Subdomain">
  <select placeholder="Select">
    <option>.example.com</option>
    <option>.example.net</option>
  </select>
  <button>Go</button>
</fieldset>

<fieldset class="group">
  <input type="text" placeholder="Search" />
  <button>Go</button>
</fieldset>

Validation error

Use data-field="error" on field containers to reveal and style error messages.

<div data-field="error">
  <label for="error-input">Email</label>
  <input type="email" aria-invalid="true" aria-errormessage="error-input"
    id="error-input" value="invalid-email" />
  <div id="error-message" class="error" role="status">Please enter a valid email address.</div>
</div>
Link

# Meter

Use <meter> for values within a known range. Browser shows colors based on low/high/optimum attributes.

<meter value="0.8" min="0" max="1" low="0.3" high="0.7" optimum="1"></meter>
<meter value="0.5" min="0" max="1" low="0.3" high="0.7" optimum="1"></meter>
<meter value="0.2" min="0" max="1" low="0.3" high="0.7" optimum="1"></meter>
Link

# Progress

Use the native <progress> element.

<progress value="60" max="100"></progress>
<progress value="30" max="100"></progress>
<progress value="90" max="100"></progress>
Link

# Spinner

Use .spinner with role="status" for loading indicators. Size with .small or .large.

<div role="status" class="spinner small"></div>
<div role="status" class="spinner"></div>
<div role="status" class="spinner large"></div>
Link

# Skeleton

Use .skeleton with role="status" for loading placeholders. Add .line for text or .box for images.

<div role="status" class="skeleton line"></div>
<div role="status" class="skeleton box"></div>

Skeleton card

Put skeleton loader inside <article> to get a card layout.

<article style="display: flex; gap: var(--space-3); padding: var(--space-6);">
  <div role="status" class="skeleton box"></div>
  <div style="flex: 1; display: flex; flex-direction: column; gap: var(--space-1);">
    <div role="status" class="skeleton line"></div>
    <div role="status" class="skeleton line" style="width: 60%"></div>
  </div>
</article>
Link

# Switch

Add role="switch" to a checkbox for toggle switch styling.

<label>
  <input type="checkbox" role="switch"> Notifications
</label>
<label>
  <input type="checkbox" role="switch" checked> Confabulation
</label>

Disabled

<label>
  <input type="checkbox" role="switch" disabled> Disabled off
</label>
<label>
  <input type="checkbox" role="switch" checked disabled> Disabled on
</label>
Link

# Table

Tables are styled by default. Use <thead> and <tbody> tags.

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Role</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Alice Johnson</td>
      <td>alice@example.com</td>
      <td>Admin</td>
      <td><span class="badge success">Active</span></td>
    </tr>
    <tr>
      <td>Bob Smith</td>
      <td>bob@example.com</td>
      <td>Editor</td>
      <td><span class="badge">Active</span></td>
    </tr>
    <tr>
      <td>Carol White</td>
      <td>carol@example.com</td>
      <td>Viewer</td>
      <td><span class="badge secondary">Pending</span></td>
    </tr>
  </tbody>
</table>
Link

# Tabs

Wrap tab buttons and panels in <ot-tabs>. Use role="tablist", role="tab", and role="tabpanel".

<ot-tabs>
  <div role="tablist">
    <button role="tab">Account</button>
    <button role="tab">Password</button>
    <button role="tab">Notifications</button>
  </div>
  <div role="tabpanel">
    <h3>Account Settings</h3>
    <p>Manage your account information here.</p>
  </div>
  <div role="tabpanel">
    <h3>Password Settings</h3>
    <p>Change your password here.</p>
  </div>
  <div role="tabpanel">
    <h3>Notification Settings</h3>
    <p>Configure your notification preferences.</p>
  </div>
</ot-tabs>
Link

# Tooltip

Use the standard title attribute on any element to render a tooltip with smooth transition.

<button title="Save your changes">Save</button>
<button title="Delete this item" class="danger">Delete</button>
<a href="#" title="View your profile">Profile</a>
Link

# Toast

Show toast notifications with ot.toast(message, options?).

<button onclick="ot.toast('Action completed successfully', 'All good', { variant: 'success' })">Success</button>
<button onclick="ot.toast('Something went wrong', 'Oops', { variant: 'danger', placement: 'top-left' })" class="danger">Danger</button>
<button onclick="ot.toast('Please review this warning', 'Warning', { variant: 'warning', placement: 'bottom-right' })" class="outline">Warning</button>
<button onclick="ot.toast('New notification', 'For your attenton', { placement: 'top-center' })">Info</button>

Placement

ot.toast('Top left', '', { placement: 'top-left' })
ot.toast('Top center', '',{ placement: 'top-center' })
ot.toast('Top right', '',{ placement: 'top-right' })  // default
ot.toast('Bottom left', '', { placement: 'bottom-left' })
ot.toast('Bottom center', '', { placement: 'bottom-center' })
ot.toast('Bottom right', '',{ placement: 'bottom-right' })

Options

OptionDefaultDescription
variant'''success', 'danger', 'warning'
placement'top-right'Position on screen
duration4000Auto-dismiss in ms (0 = persistent)

Custom markup

Use ot.toastEl(element, options?) to show toasts with custom HTML content.

<template id="undo-toast">
  <output class="toast" data-variant="success">
    <h6 class="toast-title">Changes saved</h6>
    <p>Your document has been updated.</p>
    <button class="secondary small" onclick="this.closest('.toast').remove()">Okay</button>
  </output>
</template>

<button onclick="ot.toastEl(document.querySelector('#undo-toast'), { duration: 8000 })">
  Toast with action
</button>

From a template:

ot.toastEl(document.querySelector('#my-template'))
ot.toastEl(document.querySelector('#my-template'), { duration: 8000, placement: 'bottom-center' })

Dynamic element:

const el = document.createElement('output');
el.className = 'toast';
el.setAttribute('data-variant', 'warning');
el.innerHTML = '<h6 class="toast-title">Warning</h6><p>Custom content here</p>';
ot.toastEl(el);

The element is cloned before display, so templates can be reused.

Clearing toasts

ot.toast.clear()              // Clear all
ot.toast.clear('top-right')   // Clear specific placement
Link

# Grid

A 12-column grid system using CSS grid. Use .container, .row, and .col classes. Column widths use .col-{n} where n is 1-12.

<div class="container demo-grid">
  <div class="row">
    <div class="col-4">col-4</div>
    <div class="col-4">col-4</div>
    <div class="col-4">col-4</div>
  </div>
  <div class="row">
    <div class="col-6">col-6</div>
    <div class="col-6">col-6</div>
  </div>
  <div class="row">
    <div class="col-3">col-3</div>
    <div class="col-6">col-6</div>
    <div class="col-3">col-3</div>
  </div>
  <div class="row">
    <div class="col-4 offset-2">col-4 offset-2</div>
    <div class="col-4">col-4</div>
  </div>
  <div class="row">
    <div class="col-3">col-3</div>
    <div class="col-4 col-end">col-4 col-end</div>
  </div>
</div>
Link