Modern UI primitives for agent-assisted developers.
Contextual tokens, element styles and React components, built on Google’s Modern Web Guidelines for quickly building bespoke UIs that are accessible, adaptable and fast.
Built on Google’s Modern Web Guidance.
Every primitive follows the Chrome team’s guidance for the modern web. That is why interfaces built with LoamUI are fast and accessible, a combination no other library offers. Three tenets run through everything:
Modern APIs, features and elements
Real <dialog>, <details>, the Popover API, anchor positioning, container queries. The platform’s own widgets, styled, not reimplementations of them.
Progressive enhancement
Everything works before and without JavaScript; animation and enhancement are layered on top, inside prefers-reduced-motion: no-preference and @supports.
Baseline browser support
Features are adopted from Baseline, and support claims come from compatibility data, not optimism.
Tokens. Element styles. Components.
Tokens and element styles matter as much as the components: they are what agents build downstream components and whole apps from. All three ship in one stylesheet.
Context decides, props don’t.
Components carry no size, variant or colour props. A region declares what it means (--loam-context) and components inside adopt it; a container’s width decides how components size. Drop components on a page and they fit. The Contextualism guide alone is enough for an agent to use it.
- One region declaration recolours buttons, checkboxes, focus rings, selection
- Fluid tokens size controls to their container, with no size props
- Width is the parent's layout: rows shrink-wrap, stacks stretch
/* a named region declares its meaning */.danger-zone { --loam-context: danger;} /* every LoamUI component inside adopts it: buttons, checked states, carets, focus rings. No props, no wrappers */
The hard-earned rules, already in the components.
Steeped in UX best practices. Every component page says when to use it, when not to, and why, so the judgment ships with the code.
- Form fields read label, description, error, control, so the fix is read before the answer is given
- Optional is marked in words, never with asterisks
- Buttons act; links go, and the docs hold the line between them
- A switch acts now; a checkbox acts on submit
- Disclosure exists to shorten a long page, not to hide what everyone needs
Quality that doesn’t depend on who wrote the code.
Agent-assisted development needs gates, not vibes. LoamUI’s quality bar is enforced by deterministic tooling: a stylelint config with every rule on, a contrast audit that reads the colour recipes out of the real stylesheets, and axe and interaction tests on every component. The same gates run for human and agent alike.
- Stylelint, every rule on
- Contrast audit reads the real stylesheets
- axe on every component
- Interaction tests on real markup
- TypeScript-first
- Zero runtime
/* the audit reads these weights from the stylesheet and asserts every derived pairing holds its contrast. Change a recipe and CI tells you what broke */--loam-color-primary-strong: light-dark( color-mix(in oklab, var(--loam-color-primary), oklch(0% 0 0deg) 22%), var(--loam-color-primary));
Compose, don’t configure.
Inspired by Base UI’s composition architecture: compound components expose their parts, element substitution goes through render, and icons are detected children, not slot props. Bespoke variants are compositions in your codebase, not configuration in the library.
- Parts, not prop soup: Modal.Root, Modal.Trigger, Modal.Popup
- render swaps the element, keeps the wiring
- Form controls self-wire from the surrounding Field
<Field.Root> <Field.Label>Work email</Field.Label> <Field.Description>We never share it.</Field.Description> <Field.Error>{errors.email}</Field.Error> <Input type="email" /> {/* self-wires */}</Field.Root>
Same button. Cleaner everything.
LoamUI keeps styling in one shared, cacheable stylesheet, so your markup stays readable. Utility frameworks inline dozens of classes onto every element; runtime CSS-in-JS serializes styles on each render.
import { Button, SignpostLink } from "@loamui/core"; <Button>Save changes</Button>;
One prop. Styles live in shared CSS: 0 kb runtime, works with any build tool.
// shadcn/ui + Tailwind: utilities inlined on every element<button className="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary/10 text-primary hover:bg-primary/20 h-10 px-4"> Save changes</button>;
~15 utility classes on the element, repeated everywhere, plus a build step to generate them.
Modern CSS, put to work.
No abstractions over the platform: LoamUI ships the same modern CSS features you’d reach for by hand, so nothing runs at runtime.
light-dark()One value, both themes: no duplicated theme objects.
color: light-dark(#1a1a1a, #fafafa);
color-mix()Derive every hover and tint from a single token.
color-mix( in oklab, var(--_color), #0000 )
@containerComponents respond to their container, not the viewport.
@container (width > 20rem) { … }:has()Style a parent from the state of its children.
.field:has(:invalid) { border-color: red }logical propertiesRTL-ready by default, with no hardcoded left / right.
padding-inline: 1rem; margin-block: 0.5rem;
@layerA predictable cascade: no specificity wars, no !important.
@layer loamui.components { … }clamp()Fluid type and spacing without a single media query.
font-size: clamp(1rem, 0.93rem + 0.35cqi, 1.25rem);
@scopeEncapsulation in the browser, so parts keep plain names.
@scope (.loam-Field) { label { … } }
33 components, ready to ship.
Inputs, data display, feedback, disclosures and navigation, accessible and themeable out of the box.
Start building
Install the package, import one stylesheet, and start with any component.