Skip to Content
Getting Started

Getting Started

Installation

Install the package

npm install @wire-ui/react

Wire UI has no production dependencies. React 19 is a peer dependency.

Add peer dependencies

If you don’t already have React 19:

npm install react@^19 react-dom@^19

Use a component

import { Button } from '@wire-ui/react' export function SaveButton() { return ( <Button type="submit" className="px-4 py-2 rounded bg-blue-600 text-white"> Save changes </Button> ) }

Styling approach

Wire UI ships no CSS. All interactive state is exposed through data-* attributes. Target them with any CSS approach you prefer.

<Button className=" px-4 py-2 rounded bg-blue-600 text-white transition-all [data-hover]:bg-blue-700 [data-active]:scale-95 [data-focus-visible]:ring-2 [data-focus-visible]:ring-offset-2 [data-disabled]:opacity-50 [data-disabled]:cursor-not-allowed "> Submit </Button>
/* button.module.css */ .button { padding: 0.5rem 1rem; border-radius: 0.375rem; background: #2563eb; color: white; } .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; }
import styles from './button.module.css' <Button className={styles.button}>Submit</Button>
.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:

import { Input } from '@wire-ui/react' <Input.Root value={value} onChange={setValue}> <Input.Label>Email address</Input.Label> <Input.Field type="email" placeholder="you@example.com" /> <Input.Error /> </Input.Root>

You control the DOM order. Need the error above the field? Swap them. Need a wrapper div between label and field? Add it. Wire UI never dictates structure.

Validation pattern

Wire UI components do not validate internally. You own validation — set invalidType when your logic determines the field is invalid:

const [email, setEmail] = useState('') const [invalidType, setInvalidType] = useState('') function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!email) setInvalidType('required') else if (!email.includes('@')) setInvalidType('email') else setInvalidType('') // clear the error } <Input.Root value={email} onChange={setEmail} invalidType={invalidType} errorMessage={{ required: 'Email is required', email: 'Enter a valid email address', }} > <Input.Label>Email</Input.Label> <Input.Field type="email" /> <Input.Error /> </Input.Root>

TypeScript

All prop types are exported:

import type { ButtonProps, InputRootProps, TextareaRootProps, PasswordRootProps, ModalRootProps, AccordionRootProps, SearchOption, Size, Status, } from '@wire-ui/react'
Last updated on

MIT License © 2026 wire-ui