@wire-ui/solid
v0.1.0
Published
AI-native unstyled primitives for your design system. Headless, compound components with zero CSS — style via data-* attributes. SolidJS edition.
Downloads
58
Maintainers
Readme
What is Wire UI?
Wire UI is an AI-native, headless component library. Every component ships with zero CSS — style everything using your own classes by targeting data-* attributes that reflect interactive state. AI-integrated docs with llms.txt and machine-readable API references make it built for AI-assisted workflows.
- AI-native. AI-integrated docs with
llms.txt, machine-readable API references, and MCP server support. Built for AI-assisted development. - Unstyled primitives. No colors, spacing, or fonts baked in. You own every pixel of your design.
- Compound components. Complex widgets follow the
Component.Partpattern, giving you full control over markup structure and element nesting. - State via
data-*attributes. Hover, focus, pressed, disabled, open — all exposed asdata-hover,data-focus-visible,data-active, etc. 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.
Solid edition. This is the SolidJS port of
@wire-ui/react, targeting Solid 1.9. Behaviour and data-attribute APIs match the React package exactly. See the Solid-specific notes below for the small idiomatic differences.
Documentation
Full documentation with live examples is at wire-ui.com.
Installation
npm install @wire-ui/solidPeer requirement
{
"solid-js": ">=1.9.0"
}Quick start
import { Button } from '@wire-ui/solid'
export default function App() {
return (
<Button
class="
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 |
| 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 class="[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:
import { createSignal } from 'solid-js'
import { Input } from '@wire-ui/solid'
function EmailField() {
const [email, setEmail] = createSignal('')
const [error, setError] = createSignal('')
return (
<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. The component evaluates the child to a DOM node and applies its data-* attributes and event listeners imperatively, so the child element renders as-is:
// 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>Primitives
createInteractiveState
The same primitive used internally by Button, Accordion.Trigger, Modal.Close, and the rest — exported for building your own interactive elements. Pass an options object with a disabled getter to keep the value reactive:
import { createInteractiveState } from '@wire-ui/solid'
function MyCard(props: { disabled?: boolean }) {
const state = createInteractiveState({
get disabled() { return !!props.disabled },
})
return (
<div
{...state.handlers}
{...state.dataAttributes}
class="[data-hover]:bg-gray-100 [data-active]:scale-95"
>
Card content
</div>
)
}createClickOutside
Fires a callback when the user clicks outside a referenced element. Pass a getter accessor so the listener picks up ref changes:
import { createSignal } from 'solid-js'
import { createClickOutside } from '@wire-ui/solid'
function Popover() {
const [, setOpen] = createSignal(true)
let rootEl: HTMLDivElement | undefined
createClickOutside(() => rootEl, () => setOpen(false))
return <div ref={rootEl}>Popover content</div>
}Solid-specific notes
@wire-ui/solid mirrors the React package's behaviour and data-attribute API exactly. The differences are idiomatic to Solid:
class, notclassName. Solid uses the native HTMLclassattribute throughout.createSignaloveruseState. Same controlled/uncontrolled pattern, just with accessors.- Primitives instead of hooks.
useInteractiveState→createInteractiveState,useClickOutside→createClickOutside. - No
forwardRef. Refs are passed as plain props or callbacks in Solid 1.x. Show/Forinstead of conditional / array rendering. Necessary because Solid components run once at setup time.Portalfromsolid-js/web. Used internally by Modal and Drawer in place ofreact-dom'screatePortal.- Reactive options. Where React passes plain values (e.g.
useInteractiveState({ disabled })), the Solid equivalent expects either a static value or an object with aget disabled()getter so reactivity is preserved.
Targets Solid 1.9 — Solid 2.0 is in beta as of March 2026; this package will track the 1.x line until 2.0 stabilises and a 2.x release lands.
TypeScript
All component props and utility types are exported:
import type {
ButtonProps,
InputRootProps,
TextareaRootProps,
PasswordRootProps,
ModalRootProps,
AccordionRootProps,
SearchOption,
IconName,
IconSize,
Size,
Status,
InteractiveStateOptions,
InteractiveStateResult,
} from '@wire-ui/solid'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 formatCommunity
- Follow on X: @wireuijs
Authors
- Jerald Austero (@jaoaustero)
Contributing
See the contributing guide for local development instructions and pull request guidelines.
License
MIT License © 2026 Wire UI. See LICENSE for details.
