@wire-ui/react
v0.1.6
Published
<p align="center"> <img src="https://raw.githubusercontent.com/wire-ui/wire-ui/main/apps/docs/public/images/logo/wire-ui-logo.svg" alt="Wire UI" height="52" /> </p>
Maintainers
Readme
What is Wire UI?
Wire UI is a headless component library for React 19. Every component ships with zero CSS — style everything using your own classes by targeting data-* attributes that reflect interactive state.
- Unstyled by default. No colors, spacing, or fonts baked in. You own every pixel of your design.
- State via
data-*attributes. Hover, focus, pressed, disabled, open — all exposed asdata-hover,data-focus-visible,data-active, etc. Style them with[data-hover]:bg-gray-100in Tailwind or[data-hover] { ... }in plain CSS. - Compound components. Complex widgets follow the
Component.Partpattern, giving you full control over markup structure and element nesting. - Controlled & uncontrolled. Every stateful component works both ways.
asChildpolymorphism. Merge all behaviour onto your own element — perfect for router links, icon buttons, and custom wrappers.- Consumer-owned validation. Form components expose
invalidTypeanderrorMessagebut never validate internally. Your logic, your rules.
Documentation
Full documentation with live examples is at wire-ui.com.
Installation
npm install @wire-ui/reactPeer requirements
{
"react": ">=19.0.0",
"react-dom": ">=19.0.0"
}Quick start
import { Button } from '@wire-ui/react'
export default function App() {
return (
<Button
className="
px-4 py-2 rounded-lg bg-indigo-600 text-white font-medium
[data-hover]:bg-indigo-700
[data-active]:scale-95
[data-focus-visible]:ring-2 [data-focus-visible]:ring-indigo-500
[data-disabled]:opacity-40 [data-disabled]:cursor-not-allowed
"
>
Save changes
</Button>
)
}Components
Form inputs
| Component | Description |
|---|---|
| Input | Text input with label, error, and success states |
| Textarea | Multi-line input with the same compound API as Input |
| Password | Input with a built-in show/hide toggle |
| Checkbox | Group and individual checkbox items |
| Radio | Single-selection radio group |
| Switch | Toggle on/off with a thumb element |
| OTP | One-time password input with individual slots |
| Select | Accessible select menu with groups and separators |
| Search | Search input with keyboard-navigable results list |
Overlay & dialog
| Component | Description |
|---|---|
| Modal | Dialog with portal rendering, overlay click-to-close, and Escape key |
| Drawer | Side-panel dialog — same structure as Modal |
| Dropdown | Trigger + floating menu with click-outside support |
| Tooltip | Hover/focus tooltip with configurable delay and side |
Layout & navigation
| Component | Description |
|---|---|
| Accordion | Collapsible sections — single or multiple open mode |
| Divider | Horizontal or vertical separator |
Display
| Component | Description |
|---|---|
| Alert | Dismissible alert with optional auto-dismiss |
| Avatar | Image with a text/initial fallback |
| Badge | Numeric count badge, capped at 9+ |
| Card | Container with optional color and size variants |
| Icon | SVG renderer from a consumer-supplied icon map |
| Image | Image with a loading placeholder |
| List | Ordered/unordered list with optional dividers and striping |
| ProgressBar | Accessible progress indicator |
| Rating | Interactive or read-only star rating |
| Spinner | Animated 12-dot loading indicator |
| Timeago | Relative or formatted timestamp that updates live |
Key concepts
Data attributes
Attributes are present as an empty string when active, and absent when not — never "true" or "false".
| Attribute | When present |
|---|---|
| data-hover | Mouse is over the element |
| data-focus-visible | Keyboard focus (mirrors :focus-visible) |
| data-active | Element is being pressed |
| data-disabled | Element is disabled |
| data-state | Open/closed, checked/unchecked — varies per component |
| data-invalid | Consumer-controlled via invalidType |
| data-success | Consumer-controlled via isSuccess |
Style them in Tailwind:
<Button className="[data-hover]:bg-blue-700 [data-active]:scale-95 [data-disabled]:opacity-50">Or in plain CSS:
button[data-hover] { background: #1d4ed8; }
button[data-active] { transform: scale(0.95); }Compound components
Complex widgets follow the Component.Part pattern so you control the structure:
<Input.Root value={email} onChange={setEmail} invalidType={error}>
<Input.Label>Email</Input.Label>
<Input.Field type="email" placeholder="[email protected]" />
<Input.Error />
</Input.Root>asChild polymorphism
Pass asChild to merge behaviour onto your own element:
// Renders as <a> but with all Button data attributes
<Button asChild>
<a href="/dashboard">Go to dashboard</a>
</Button>Consumer-owned validation
Set invalidType to a key and the component renders the matching error message — no internal validation ever runs:
<Input.Root
invalidType={error} // e.g. "required" or "email"
errorMessage={{
required: 'Email is required',
email: 'Enter a valid email address',
}}
>
<Input.Field type="email" />
<Input.Error /> {/* renders the matching message */}
</Input.Root>Hooks
useInteractiveState
The same hook used internally by Button, Accordion.Trigger, and Modal.Close — exported for building your own interactive elements.
import { useInteractiveState } from '@wire-ui/react'
function MyCard({ disabled }: { disabled?: boolean }) {
const { handlers, dataAttributes } = useInteractiveState({ disabled })
return (
<div
{...handlers}
{...dataAttributes}
className="[data-hover]:bg-gray-100 [data-active]:scale-95"
>
Card content
</div>
)
}useClickOutside
Fires a callback when the user clicks outside a referenced element.
import { useRef } from 'react'
import { useClickOutside } from '@wire-ui/react'
function Popover() {
const ref = useRef<HTMLDivElement>(null)
useClickOutside(ref, () => setOpen(false))
return <div ref={ref}>Popover content</div>
}TypeScript
All component props and utility types are exported:
import type {
ButtonProps,
InputRootProps,
TextareaRootProps,
PasswordRootProps,
ModalRootProps,
AccordionRootProps,
SearchOption,
IconSize,
Size,
Status,
InteractiveStateOptions,
InteractiveStateResult,
} from '@wire-ui/react'Development
# Install dependencies (from monorepo root)
npm install
# Run Storybook
npm run storybook
# Unit tests (watch mode)
npm test
# Unit tests (single run)
npm run test:run
# Unit tests with coverage
npm run test:coverage
# Type check + build
npm run build
# Lint
npm run lint
# Format
npm run formatAuthors
- Jerald Austero (@jaoaustero)
Contributing
See the contributing guide for local development instructions and pull request guidelines.
License
MIT License © 2025 Wire UI. See LICENSE for details.
