v0.4.6 · canary
Wayra-UI
React 19 components whose whole styling API is one class and a few data attributes. A theme is a block of CSS, so everything restyles without a provider, a build step, or a line of JavaScript — and every field is already wired to react-hook-form.
pnpm i wayra-ui
Click any package manager to copy the install command
import { zodResolver } from "@hookform/resolvers/zod";import { Button, Checkbox, Form, Input, InputPassword } from "wayra-ui";import { useState } from "react";import { useForm, useFormState } from "react-hook-form";import { schema, type Values } from "./schema";
export function SignInForm() { const [status, setStatus] = useState<string>(); const form = useForm<Values>({ defaultValues: { email: "", password: "", remember: true }, resolver: zodResolver(schema), }); const { isSubmitting } = useFormState({ control: form.control });
async function signIn(values: Values) { setStatus(undefined); await new Promise((resolve) => setTimeout(resolve, 700)); setStatus(`Signed in as ${values.email}.`); }
return ( <Form form={form} onSubmit={signIn} className="hu-landing-form" noValidate> <Input name="email" type="email" label="Email" placeholder="you@wayra.dev" autoComplete="email" width="block" /> <InputPassword name="password" label="Password" width="block" /> <Checkbox name="remember" label="Remember me for 30 days" /> <Button type="submit" width="block" loading={isSubmitting}> Sign in </Button> {status ? <p className="hu-readout">{status}</p> : null} </Form> );}import * as z from "zod/mini";
export const schema = z.object({ email: z.email("Enter a valid email address."), password: z.string().check(z.minLength(8, "At least eight characters.")), remember: z.boolean(),});
export type Values = z.infer<typeof schema>;What makes it different
Section titled “What makes it different”A theme is a block of CSS
Put data-theme on any element and its subtree repaints — no provider,
no context, no hydration. Every theme in the box (7 of them) is a swatch
below, and each swatch is just a span wearing the attribute:
Semantics first
Components start from the element the browser already ships; ARIA only where nothing native exists. axe runs on every story in CI, and no story suppresses a rule.
- <select>Select
- <details>Collapse, Accordion
- <progress>Progress
- <input type="radio">Radio
Forms already wired
Every field wires itself by name — no Controller to wrap,
no adapter to install. Form owns only the provider and
handleSubmit, so the useForm instance and the resolver stay
yours.
Server-ready, compiler-fast
Every chunk ships with use client, so components import straight into
App Router pages — no wrapper file, no directive to write. The build runs the React
Compiler, so they arrive memoized.
Write your own theme
A theme is sixteen colour tokens and a few knobs under one selector — write the block, name it, ship it. The theming guide walks through every token.
- Components
- 39
- Themes in the box
- 7
- Cascade layers
- 2
- Lines of theming JS
- 0
Every component
Section titled “Every component”Actions
4Inputs
16Feedback
7Data display
6Layout
5Navigation
2Overlay
2The whole styling API
Section titled “The whole styling API”Two attributes and a class. That is all the stylesheet looks for, in React or in plain HTML. Colours are OKLCH under the hood, the cascade is two layers so your own unlayered CSS always wins, and there is no utility framework to adopt.
Every section of this page is that same pair: the cards, the swatches, the stats and the counts above are hand-written markup with no React behind them. View the source of this page and there is nothing else to find.
<link rel="stylesheet" href="wayra-ui/styles.css" />
<button class="wayra-button" data-variant="filled" data-color="success">Save</button><span class="wayra-badge" data-variant="outline">Beta</span>