# 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 one Content 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' React Vue ``` 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 drawer Settings 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 item Second 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 action This 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 found Searching... {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' Fruits Apple Banana Other ``` --- ### 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.