# Wire UI — Full Documentation
> A headless, unstyled React 19 component library. Style everything with your own CSS using data-* attributes that reflect interactive state.
Wire UI provides behavior primitives for React applications. Components handle interaction states, accessibility, and complex behaviors while shipping zero CSS. Style them with Tailwind, CSS Modules, or plain CSS — anything that supports attribute selectors.
Package: @wire-ui/react
npm: https://www.npmjs.com/package/@wire-ui/react
GitHub: https://github.com/wire-ui/wire-ui
---
## Introduction
### Core Principles
**Unstyled by default.** No CSS shipped. No design opinions. You own the design system.
**State via data-* attributes.** Every interactive state (hover, focus, active, disabled, open/closed) is exposed as a data attribute — empty string when active, absent when not. Style with CSS attribute selectors.
**Compound components.** Complex widgets follow the `Component.Part` pattern. You compose the markup, control the order, and add whatever wrapper elements you need.
**Consumer-owned validation.** Form components expose `invalidType` and `errorMessage` props but perform no validation internally. Set `invalidType` when your validation logic decides it's invalid — the component renders the error state.
**asChild for polymorphism.** Pass `asChild` to Button to merge all props onto your own child element — great for router links and icon buttons.
---
## Getting Started
### Installation
```bash
npm install @wire-ui/react
```
Wire UI has no production dependencies. React 19 is a peer dependency.
### Styling Approach
Wire UI ships no CSS. All interactive state is exposed through data-* attributes. Target them with any CSS approach you prefer.
#### Tailwind CSS
```tsx
```
#### CSS Modules
```css
.button[data-hover] { background: #1d4ed8; }
.button[data-active] { transform: scale(0.95); }
.button[data-focus-visible] { outline: 2px solid #2563eb; }
.button[data-disabled] { opacity: 0.5; cursor: not-allowed; }
```
### Compound Components
Complex components use the `Component.Part` pattern. You compose the markup yourself:
```tsx
import { Input } from '@wire-ui/react'
Email address
```
### Validation Pattern
Wire UI components do not validate internally. You own validation:
```tsx
const [email, setEmail] = useState('')
const [invalidType, setInvalidType] = useState('')
function handleSubmit(e) {
e.preventDefault()
if (!email) setInvalidType('required')
else if (!email.includes('@')) setInvalidType('email')
else setInvalidType('')
}
Email
```
### TypeScript
All prop types are exported:
```ts
import type {
ButtonProps, InputRootProps, TextareaRootProps, PasswordRootProps,
ModalRootProps, AccordionRootProps, SearchOption, Size, Status,
} from '@wire-ui/react'
```
---
## Data Attributes
All interactive state is exposed through data-* attributes. An attribute is present as an empty string when active and absent when not — never "true" or "false".
```css
[data-hover] { ... } /* correct */
[data-hover="true"] { ... } /* never matches */
```
### Universal Interactive Attributes
These are set by useInteractiveState and are available on Button and any component using the hook.
| Attribute | When present |
|--------------------|------------------------------------------------------|
| data-hover | Mouse is over the element |
| data-focus-visible | Element has keyboard focus (matches :focus-visible) |
| data-active | Element is being pressed |
| data-disabled | Element is disabled |
| data-autofocus | Element was rendered with the autoFocus prop |
### Form Field Attributes
| Attribute | Component | When present |
|--------------|------------------------------|-------------------------------------------|
| data-active | Input, Textarea, Password | Field is focused |
| data-invalid | Input, Textarea, Password | invalidType prop is set |
| data-success | Input, Textarea | isSuccess prop is true |
### State Attributes
| Attribute | Component | Values |
|-----------------|--------------------------------------------------------------------|---------------------------------|
| data-state | Modal, Drawer, Accordion Item/Trigger/Content, Dropdown, Tooltip | "open"/"closed" or "checked"/"unchecked" |
| data-visible | Password.Toggle | Present when password visible |
| data-loading | Search.Root | Present when loading prop true |
| data-highlighted| Search.Item | Present on keyboard-focused item|
| data-complete | OTP.Root | Present when all slots filled |
---
## Hooks
### useInteractiveState
Tracks hover, keyboard focus, and press state for any element. Same hook used internally by Button — exported so you can build your own interactive components.
```tsx
import { useInteractiveState } from '@wire-ui/react'
function MyCard({ disabled }) {
const { handlers, dataAttributes } = useInteractiveState({ disabled })
return (
Card content
)
}
```
Returns: `handlers` (event handlers), `dataAttributes` (data-hover, data-focus-visible, data-active, data-disabled), `isHovered`, `isFocusVisible`, `isActive`.
### useClickOutside
Fires a callback when the user clicks outside a referenced element. Used internally by Dropdown and Search.
```tsx
import { useRef } from 'react'
import { useClickOutside } from '@wire-ui/react'
function Popover() {
const ref = useRef(null)
useClickOutside(ref, () => setOpen(false))
return
Popover content
}
```
Parameters: `ref` (RefObject), `handler` (() => void).
---
## Components
### Accordion
Collapsible sections. Supports `single` (one item open at a time) and `multiple` (many open at once). Controlled and uncontrolled.
```tsx
import { Accordion } from '@wire-ui/react'
Section oneContent for section one.
```
Props (Root): type ("single" | "multiple"), collapsible (boolean), defaultValue (string), value (string), onValueChange (fn).
Data attributes: data-state ("open" | "closed") on Item, Trigger, Content.
---
### Alert
Dismissible alert with optional auto-dismiss timer. Status variants applied via data-status.
```tsx
import { Alert } from '@wire-ui/react'
setVisible(false)}
autoDismiss={5000}
>
Operation completed successfully.✕
```
Props: onDismiss (fn), autoDismiss (number ms). Data attributes: data-status (consumer-set: "success" | "warning" | "danger").
---
### Avatar
Image with a lazy-loading fallback. Fallback renders automatically when src is empty or the image fails to load.
```tsx
import { Avatar } from '@wire-ui/react'
JD
```
Data attributes: data-status ("loading" | "loaded" | "error") on Avatar.Image.
---
### Badge
Numeric count badge. Caps display at 9+. Renders nothing when count is 0 or negative.
```tsx
import { Badge } from '@wire-ui/react'
{/* Badge renders the count */}
```
Props: count (number). Data attributes: data-count (raw number value).
---
### Button
A native button with full interactive state tracking. Use asChild to render as any element.
```tsx
import { Button } from '@wire-ui/react'
// Polymorphic — renders as anchor
```
Props: asChild (boolean), disabled (boolean), all native button props.
Data attributes: data-hover, data-focus-visible, data-active, data-disabled, data-autofocus.
---
### Card
A simple unstyled container element. Pass data-color and data-size to drive design token styles.
```tsx
import { Card } from '@wire-ui/react'
Card content
```
---
### Checkbox
Multi-select checkbox group. Controlled via value: (string | number)[] and onChange.
```tsx
import { Checkbox } from '@wire-ui/react'
ReactVue
```
Props (Group): value (array), onChange (fn). Data attributes: data-checked on Item/Indicator when selected.
---
### Divider
Horizontal or vertical separator. Decorative by default (aria-hidden). Pass decorative={false} for a semantic separator.
```tsx
import { Divider } from '@wire-ui/react'
```
Props: orientation ("horizontal" | "vertical"), decorative (boolean).
---
### Drawer
Side-panel overlay. Same structure as Modal. Closes on overlay click or Escape key.
```tsx
import { Drawer } from '@wire-ui/react'
Open drawerSettings✕
Panel content
```
Data attributes: data-state ("open" | "closed") on Panel, Backdrop.
---
### Dropdown
Trigger + menu pattern. Closes on Escape key, outside click, and menu item selection.
```tsx
import { Dropdown } from '@wire-ui/react'
Options handleEdit()}>Edit handleDelete()}>Delete handleArchive()}>Archive
```
Props (Root): open (boolean), onOpenChange (fn), defaultOpen (boolean).
Data attributes: data-state ("open" | "closed") on Menu.
---
### Icon
Renders an SVG from a consumer-supplied icon map. Wire UI ships no SVG assets — you bring your own.
```tsx
import { Icon } from '@wire-ui/react'
const icons = {
check: '',
}
```
Props: name (string), icons (Record), size ("small" | "medium" | "large").
---
### Image
Image wrapper with a loader placeholder shown until the image fires a load event.
```tsx
import { Image } from '@wire-ui/react'
```
Data attributes: data-loaded on Img after successful load, data-position on Root.
---
### Input
Compound component for text inputs. Validation is entirely consumer-controlled.
```tsx
import { Input } from '@wire-ui/react'
Email
```
Props (Root): value, onChange, invalidType (string), errorMessage (Record), isSuccess (boolean).
Data attributes: data-active (focused), data-invalid, data-success on Field.
---
### List
Ordered or unordered list container. Variant attributes used as CSS hooks.
```tsx
import { List } from '@wire-ui/react'
First itemSecond item
```
Data attributes: data-type, data-size, data-striped, data-divider on Root.
---
### Modal
Compound modal dialog. Renders into a portal, closes on overlay click or Escape key.
```tsx
import { Modal } from '@wire-ui/react'
Confirm actionThis cannot be undone.
```
Props (Root): open (boolean), onOpenChange (fn), defaultOpen (boolean).
Data attributes: data-state ("open" | "closed") on Panel, Backdrop.
---
### OTP
One-time password input. Auto-advances on type, handles Backspace, fires onComplete when all slots filled.
```tsx
import { OTP } from '@wire-ui/react'
verifyCode(value)}
className="flex gap-2 [data-complete]:ring-2 [data-complete]:ring-green-500"
>
{Array.from({ length: 6 }).map((_, i) => (
))}
```
Props (Root): length (number), onComplete (fn), alphanumeric (boolean).
Data attributes: data-complete on Root when all slots filled, data-active on focused Slot.
---
### Password
Password input with a built-in show/hide toggle. Validation is consumer-controlled.
```tsx
import { Password } from '@wire-ui/react'
Password
{/* data-visible is present when password is shown */}
Show
```
Data attributes: data-visible on Toggle when password is visible.
---
### ProgressBar
Accessible progress indicator. Renders role="progressbar" with aria-valuenow, aria-valuemin, aria-valuemax.
```tsx
import { ProgressBar } from '@wire-ui/react'
```
Props (Root): value (number), max (number, default 100).
---
### Radio
Single-selection radio group. Controlled via value and onChange.
```tsx
import { Radio } from '@wire-ui/react'
{['sm', 'md', 'lg'].map((size) => (
{size.toUpperCase()}
))}
```
Data attributes: data-checked on Item/Indicator when selected.
---
### Rating
Star rating component. Hover preview shows the prospective rating before clicking.
```tsx
import { Rating } from '@wire-ui/react'
{Array.from({ length: 5 }).map((_, i) => (
))}
```
Props (Root): value (number), onChange (fn), max (number), readOnly (boolean), disabled (boolean).
Data attributes: data-filled, data-hover on Star.
---
### Search
Search input with a dropdown results list. Keyboard navigation with Arrow keys, Enter to select, Escape to close.
```tsx
import { Search } from '@wire-ui/react'
fetchResults(q)}
onSelect={(item) => handleSelect(item)}
loading={isLoading}
>
No results foundSearching...
{results.map((item) => (
{item.label}
))}
```
Data attributes: data-loading on Root, data-highlighted on the keyboard-focused Item.
---
### Select
Accessible select menu with groups, separators, and a fully custom trigger.
```tsx
import { Select } from '@wire-ui/react'
FruitsAppleBananaOther
```
---
### Spinner
Animated 12-dot loading indicator. Renders role="status" with aria-label="Loading".
```tsx
import { Spinner } from '@wire-ui/react'
```
Props: size ("small" | "medium" | "large"), color (CSS color string).
---
### Switch
Toggle component with a thumb element. Renders as button role="switch" with aria-checked.
```tsx
import { Switch } from '@wire-ui/react'
```
Data attributes: data-state ("checked" | "unchecked") on Root and Thumb.
---
### Textarea
Same compound API as Input, renders a textarea. Controlled, uncontrolled, consumer-controlled error state.
```tsx
import { Textarea } from '@wire-ui/react'
Message
```
---
### Timeago
Relative or formatted timestamp. In isDuration mode it updates live via setInterval.
```tsx
import { Timeago } from '@wire-ui/react'
// Relative: "3 minutes ago"
// Live countdown
// Formatted: "Jan 15, 2025"
```
Props: date (Date | string | number), isDuration (boolean), format (string), updateInterval (number ms).
---
### Tooltip
Hover/focus tooltip. Configurable delay and side.
```tsx
import { Tooltip } from '@wire-ui/react'
This is a tooltip
```
Props (Root): delayDuration (number ms), open (boolean), onOpenChange (fn).
Props (Content): side ("top" | "bottom" | "left" | "right").
Data attributes: data-state ("open" | "closed") on Content, data-side on Content.