Headless primitives
Use @comwit/ui directly when you want to own all styling and composition.
The engine curates rather than reinvents: positioning comes from Floating UI, the autocomplete and labels from React Aria, the rich-text editor from Tiptap, modal scroll locking from react-remove-scroll, and the chat list from React Virtuoso.
npm install @comwit/ui
'use client'
import { Dialog } from '@comwit/ui'
export function Example() {
return (
<Dialog.Root>
<Dialog.Trigger>Open settings</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="dialog-overlay" />
<Dialog.Content className="dialog-content">
<Dialog.Title>Settings</Dialog.Title>
<Dialog.Description>Update your workspace preferences.</Dialog.Description>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
)
}
Supply your own CSS for the overlay and content. The primitive supplies state, focus handling, keyboard behavior, and accessibility attributes.
Compound components use namespaces such as Dialog.Root, Select.Trigger, and Tabs.Content. Basic controls such as Button, Input, and Textarea are flat exports.
The engine also owns the app-shell and conversation behavior that the styled templates only skin: AppBar.Root (flow / pinned / reveal → data-state), BottomNav.Root / Item (selection, compact on scroll, expand on touch), BottomSheet.Root / Content / Handle (Dialog plus drag-to-dismiss; distance in --sheet-drag, progress in --sheet-drag-progress, data-dragging while the finger is down), DragScroller.Root / Track (drag, flick momentum, wheel, arrow keys), PullToRefresh.Root / Scroller / Indicator (the pull gesture; distance in --ptr-pull, state in data-state), AppShell.Root / Scroller / Indicator (the one <main> scroller with its structural styles forced inline, the shared scroll intent and the refresh gesture in one piece), NavDepthProvider / useBackNavigation (history-aware back: back inside the app, replace to the parent on direct entry), and Chat.Root / List / Message / Bubble / Composer (virtualized list, messenger and assistant scroll rules, Enter and IME handling). The hooks ScrollChromeProvider / useScrollChrome, useMobile and useGlassLens are flat exports. Each primitive announces its state through data-* attributes, which is what the templates style.
Overlay parts (Dialog, BottomSheet, Popover, DropdownMenu and Select content and overlays) can animate in and out without an animation library. Put an element under the part with asChild and give it usePresenceAnimation({ hidden, visible, enter, exit }): the keyframes run through the browser's Web Animations API from the first open, the part stays mounted until the exit finishes, and a close that is reversed halfway turns back from where it is. With reduced motion only the opacity changes.
For ready-to-customize styles, use the CLI installation flow. The component examples show the styled layer.