Keep Oat’s semantic HTML, tiny footprint, and token system. Add Oatbase when a product needs richer selection, navigation, content, or application patterns. The complete Oat-inclusive CSS and JavaScript stays under 30 kB gzipped; the extension-only layer stays under 20 kB. Individual entries let applications pay only for the pieces they use.
Complete pages assembled from shipped Oat and Oatbase primitives. Each preview runs in an opaque-origin sandbox with no access to the documentation page; switch to Code for the full document.
Getting started
Installation
Oatbase has not been published to a package registry or CDN yet. Use the current source checkout locally while the first release is prepared.
Local package
Enter nix develop to use the checked-in development shell, or install Bun, Node.js/npm, and GNU Make. Then install dependencies and build the distribution from the Oatbase directory:
npm ci
make dist
Then install that directory from your application. Adjust the relative path to match your checkout:
Projects that already load Oat can add only the extensions they use. The extension-only aggregate is also available as @oddship/oatbase/extensions.css and @oddship/oatbase/extensions.
import '@knadh/oat/oat.min.css';
import '@knadh/oat/oat.min.js';
import '@oddship/oatbase/combobox.css';
import '@oddship/oatbase/combobox';
// CSS-only additions need no JavaScript import.
import '@oddship/oatbase/drawer.css';
import '@oddship/oatbase/item.css';
Getting started
Principles
Oatbase extends Oat without creating a second component system on top of it.
Native first
If an Oat or native HTML element solves the problem, use it. The drawer is CSS around <dialog>; the combobox keeps a real <select> as its form control and fallback.
Progressive enhancement
Forms still submit and essential controls remain usable before JavaScript loads. JavaScript adds search, keyboard movement, and richer interaction rather than basic access.
Small pieces
Every component is independently loadable. The aggregate bundle is a convenience, not a requirement.
Oat tokens
Oatbase introduces no parallel theme. It consumes variables such as --card, --border, --accent, Oat’s spacing scale, and its --radius-* scale. The documentation intentionally preserves those radius defaults.
Getting started
Customizing
Oat owns the visual language. Oatbase inherits it and exposes only the component geometry that Oat cannot describe.
Theme contract
Override Oat’s semantic variables after the default Oatbase stylesheet. With extension-only imports, place the overrides after Oat and before or after the extensions. The same palette, type, spacing, radius, shadow, and motion values flow through both layers.
Oatbase ships four alternative token maps plus an explicit Oat reset for nested scopes. The top-bar theme picker provides five choices. Presets contain no component selectors or JavaScript.
Theme
Character
Oat
The unchanged Oat foundation.
Doordarshan
Monospace, terracotta, saffron, peacock, and warm terminal surfaces.
Forest
Quiet greens and natural neutral surfaces.
Ocean
Cool blue surfaces with cyan accents.
Paper
Warm editorial surfaces with a serif stack and restrained corners.
/* All presets, including the scoped Oat reset */
import '@oddship/oatbase/themes';
/* Or one independently */
import '@oddship/oatbase/themes/doordarshan.css';
document.documentElement.dataset.oatTheme = 'doordarshan';
Scoped theme lab
The preset select and native radio controls apply the shipped variables only to the preview. The rest of the documentation keeps its top-bar selection.
Component geometry hooks
Oatbase variables are prefixed and have local fallbacks. Override them globally, within a subtree, or on one component.
<ot-theme-switcher> selects system, light, or dark appearance by setting color-scheme. Explicit light and dark choices also set data-theme on the document root so scoped Oat overrides work as documented.
Component · JavaScript
Command
A command palette with local or application-supplied results, keyboard navigation, and an optional global shortcut.
Call event.preventDefault() to keep the palette open after selection.
Application-supplied results
Set data-filter="manual" when results come from an API or application index. Oatbase emits each query without hiding existing rows. Replace or update the semantic list, then call refresh() so keyboard focus, aria-activedescendant, groups, and the empty state reflect the new DOM.
The application owns fetching, cancellation, caching, and result markup. Each selectable result still uses data-command-item. A listener may also cancel oatbase:query to suppress local filtering for one query.
Global shortcut
Add data-shortcut="mod+k" to open with Command-K on macOS or Control-K elsewhere.
API
Surface
Purpose
data-shortcut="mod+k"
Registers the optional global Command-K or Control-K shortcut.
data-command-open / data-command-close
Opt existing buttons into opening or closing the palette.
data-command-search / data-command-list
Identify the search input and semantic result list.
data-command-item
Marks a selectable result. Use aria-disabled="true" for an unavailable result.
data-value / data-keywords
Provide the emitted value and additional searchable text.
data-filter="manual"
Leaves filtering and result insertion to the application; Oatbase continues to own listbox navigation and selection.
data-keep-open
Keeps the dialog open after that item is accepted.
query
Returns the current trimmed query string.
items / visibleItems
Expose all result elements or the currently available filtered results.
open() / close()
Control the native dialog programmatically.
refresh()
Reindexes inserted results and synchronizes roles, active item, groups, empty state, and aria-activedescendant.
oatbase:query
Cancelable event with { query }. Canceling it skips local filtering for that input.
oatbase:select
Cancelable event with { value, item }; preventing default keeps the palette open.
oatbase:open / oatbase:close
Report palette lifecycle changes.
Component · CSS only
Drawer
A native dialog styled as a sheet from the top, right, bottom, or left edge.
Use <kbd> for each physical key and wrap combinations in .kbd-group. Set data-variant="inline" when a shortcut is embedded inside another compact control and should not create a nested keycap surface. Keyboard hints are documentation; wire the actual shortcut separately.