| name | beatui-usage |
| description | Build UIs with the @tempots/beatui component library. Use when writing application code that imports or uses BeatUI components — buttons, forms, overlays, layout, tables, icons, theming, notifications, or routing. |
Role
You are an expert at building applications with the @tempots/beatui component library and the @tempots/dom reactive framework. You know the component APIs, the form system, theming, notifications, icons, and how to compose them into full application UIs.
BeatUI components are plain TypeScript functions — no JSX, no class syntax. They return TNode values composed via @tempots/dom element factories.
Installation & Setup
Dependencies
pnpm add @tempots/beatui @tempots/dom @tempots/ui @tempots/std @tempots/core
App Root
Wrap your app in BeatUI() — it sets up theme, locale, routing, i18n, and notifications:
import { render } from '@tempots/dom'
import { BeatUI } from '@tempots/beatui'
render(
BeatUI(
{
enableAppearance: true,
defaultAppearance: 'system',
includeNotifications: true,
notificationPosition: 'bottom-end',
includeAuthI18n: false,
},
MyApp()
),
document.getElementById('app')!
)
CSS Import
With Tailwind v4 (recommended):
import tailwindcss from '@tailwindcss/vite'
import { beatuiTailwindPlugin } from '@tempots/beatui/tailwind/vite-plugin'
export default defineConfig({
plugins: [
tailwindcss(),
beatuiTailwindPlugin({
semanticColors: {
primary: 'sky',
secondary: 'cyan',
},
}),
],
})
@import 'tailwindcss';
@custom-variant dark (&:is(.dark *));
Without Tailwind:
import '@tempots/beatui/css'
@tempots/dom Essentials
Reactivity
import { prop, computedOf, Value } from '@tempots/dom'
const count = prop(0)
count.set(5)
count.update(n => n + 1)
const doubled = count.map(n => n * 2)
Value.get(count)
Value.on(count, v => console.log(v))
DOM Construction
import { html, attr, style, on, aria } from '@tempots/dom'
html.div(
attr.class('my-class'),
attr.id('main'),
style.color('red'),
on.click((e, ctx) => console.log('clicked')),
aria.label('Main content'),
html.span('Hello'),
html.p('World')
)
Conditional & Dynamic Rendering
import { When, Fragment, Empty, MapSignal } from '@tempots/dom'
When(isVisible, () => html.div('Shown'), () => html.div('Hidden'))
MapSignal(items, itemList =>
html.ul(...itemList.map(item => html.li(item.name)))
)
onBlur ? on.blur(handler) : Empty
Fragment(child1, child2, child3)
Providers
import { Use, Provide } from '@tempots/dom'
Use(Theme, ({ appearance, setAppearancePreference }) => {
return html.div(appearance.map(a => `Current: ${a}`))
})
CRITICAL Rules
- NEVER return
computedOf(...) as a root TNode — use When() or MapSignal() for conditional/dynamic rendering.
- NEVER use
computedOf(...) to conditionally return attribute nodes.
- DO use
When(condition, thenFn, elseFn) for conditional rendering.
- DO use
MapSignal(signal, fn) for reactive child content.
- ALWAYS wrap
Collapse() in its own html.div() wrapper.
Available Entry Points
| Import | Purpose |
|---|
@tempots/beatui | Core components (buttons, forms, layout, overlays, data, navigation, typography) |
@tempots/beatui/auth | Authentication UI components |
@tempots/beatui/json-schema | JSON Schema form generation |
@tempots/beatui/json-structure | JSON structure forms |
@tempots/beatui/monaco | Monaco editor integration |
@tempots/beatui/markdown | Markdown rendering |
@tempots/beatui/prosemirror | ProseMirror editor integration |
@tempots/beatui/lexical | Lexical editor integration |
@tempots/beatui/codehighlight | Code syntax highlighting |
@tempots/beatui/better-auth | Better-auth bridge layer |
@tempots/beatui/tailwind | Tailwind preset & Vite plugin |
@tempots/beatui/css | Pre-built CSS stylesheet |
Component Patterns
All components follow: ComponentName(options, ...children): TNode
Options accept Value<T> — pass static values or reactive signals interchangeably.
Buttons
import { Button, CloseButton, ToggleButton, Icon } from '@tempots/beatui'
Button({ variant: 'filled', color: 'primary', size: 'md' }, 'Save')
Button(
{ variant: 'outline', onClick: () => save() },
Icon({ icon: 'lucide:save', size: 'sm' }),
'Save'
)
Button({ loading: true, disabled: true }, 'Saving...')
CloseButton({ onClick: () => close(), size: 'sm' })
const active = prop(false)
ToggleButton({ value: active, onChange: v => active.set(v) }, 'Bold')
Form Inputs
import { TextInput, NumberInput, CheckboxInput, Switch, NativeSelect, TextareaInput } from '@tempots/beatui'
import { prop } from '@tempots/dom'
const name = prop('')
TextInput({
value: name,
onInput: v => name.set(v),
placeholder: 'Enter name',
size: 'md',
disabled: false,
hasError: false,
})
const age = prop(0)
NumberInput({ value: age, onChange: v => age.set(v), min: 0, max: 120 })
const agreed = prop(false)
CheckboxInput({ value: agreed, onChange: v => agreed.set(v), label: 'I agree' })
const enabled = prop(true)
Switch({ value: enabled, onChange: v => enabled.set(v), label: 'Enable feature' })
const role = prop('editor')
NativeSelect({
value: role,
onChange: v => role.set(v),
options: [
{ value: 'admin', label: 'Administrator' },
{ value: 'editor', label: 'Editor' },
{ value: 'viewer', label: 'Viewer' },
],
})
const bio = prop('')
TextareaInput({ value: bio, onInput: v => bio.set(v), rows: 4 })
TextInput({
value: search,
onInput: v => search.set(v),
before: Icon({ icon: 'lucide:search', size: 'sm' }),
after: CloseButton({ onClick: () => search.set(''), size: 'xs' }),
})
Form System (with Validation)
import { useForm, Control, TextInput, NumberInput } from '@tempots/beatui'
import { z } from 'zod'
const { controller } = useForm({
schema: z.object({
name: z.string().min(1, 'Required'),
email: z.string().email('Invalid email'),
age: z.number().min(18, 'Must be 18+'),
}),
validationMode: 'eager',
initialValue: { name: '', email: '', age: 0 },
})
const nameCtrl = controller.field('name')
const emailCtrl = controller.field('email')
const ageCtrl = controller.field('age')
html.form(
Control(TextInput, {
controller: nameCtrl,
label: 'Full Name',
description: 'As shown on your ID',
layout: 'vertical',
}),
Control(TextInput, {
controller: emailCtrl,
label: 'Email',
}),
Control(NumberInput, {
controller: ageCtrl,
label: 'Age',
}),
Button(
{ type: 'submit', disabled: controller.hasError },
'Submit'
)
)
Controller API:
ctrl.signal.value
ctrl.hasError
ctrl.touched
ctrl.dirty
ctrl.errorVisible
ctrl.disabled
Array fields:
const tagsCtrl = controller.field('tags').array()
tagsCtrl.push('new-tag')
tagsCtrl.removeAt(0)
tagsCtrl.move(from, to)
tagsCtrl.item(0)
tagsCtrl.length.value
Overlays
import { Modal, Drawer, Tooltip, Popover, Button } from '@tempots/beatui'
Modal(
{ size: 'md', position: 'center', dismissable: true, showCloseButton: true },
(open, close) =>
Button(
{
onClick: () => open({
header: html.h2('Confirm Action'),
body: html.p('Are you sure you want to proceed?'),
footer: Fragment(
Button({ variant: 'default', onClick: close }, 'Cancel'),
Button({ variant: 'filled', color: 'danger', onClick: () => { doIt(); close() } }, 'Delete'),
),
}),
},
'Open Modal'
)
)
Drawer(
{ side: 'right', size: 'md' },
(open, close) =>
Button({ onClick: () => open({ body: SettingsPanel() }) }, 'Settings')
)
Tooltip({ content: 'Helpful info', position: 'top' },
Button({}, 'Hover me')
)
Popover(
{ position: 'bottom', trigger: 'click' },
(open, close) => Button({ onClick: open }, 'Click me'),
html.div('Popover content')
)
Layout
import { Card, CardHeader, CardBody, CardFooter, Accordion, AccordionItem, Collapse, AppShell } from '@tempots/beatui'
Card(
{ variant: 'elevated', size: 'md' },
CardHeader({}, html.h3('Title')),
CardBody({}, html.p('Content here')),
CardFooter({}, Button({}, 'Action'))
)
Accordion(
{ multiple: false },
AccordionItem({ label: 'Section 1' }, html.p('Content 1')),
AccordionItem({ label: 'Section 2' }, html.p('Content 2')),
)
const isOpen = prop(false)
html.div(
Collapse({ open: isOpen }, html.div('Collapsible content'))
)
Data Display
import { Table, Tabs, Tab, Tree, Badge, Avatar, Pagination } from '@tempots/beatui'
Table(
{ hoverable: true, withStripedRows: true, fullWidth: true, size: 'md' },
html.thead(html.tr(html.th('Name'), html.th('Email'))),
html.tbody(
html.tr(html.td('Alice'), html.td('alice@example.com')),
html.tr(html.td('Bob'), html.td('bob@example.com')),
)
)
const activeTab = prop('tab1')
Tabs(
{ value: activeTab, onChange: v => activeTab.set(v) },
Tab({ value: 'tab1', label: 'Overview' }, html.p('Overview content')),
Tab({ value: 'tab2', label: 'Settings' }, html.p('Settings content')),
)
Badge({ color: 'success', variant: 'filled' }, 'Active')
Avatar({ src: '/photo.jpg', name: 'Alice', size: 'md' })
const page = prop(1)
Pagination({ value: page, total: 100, pageSize: 10, onChange: v => page.set(v) })
Navigation
import { Link, Breadcrumbs, BreadcrumbItem, NavigationProgress } from '@tempots/beatui'
Link({ href: '/dashboard', color: 'primary' }, 'Dashboard')
Link({ href: 'https://example.com', target: '_blank' }, 'External')
Breadcrumbs({},
BreadcrumbItem({ href: '/' }, 'Home'),
BreadcrumbItem({ href: '/products' }, 'Products'),
BreadcrumbItem({}, 'Widget'),
)
Icons
import { Icon } from '@tempots/beatui'
Icon({
icon: 'lucide:home',
size: 'md',
color: 'primary',
tone: 'solid',
title: 'Home',
})
Notifications
import { NotificationService } from '@tempots/beatui'
NotificationService.show(
{ title: 'Saved', color: 'success', icon: 'lucide:check', dismissAfter: 3 },
'Your changes have been saved.'
)
NotificationService.show(
{ title: 'Error', color: 'danger', icon: 'lucide:alert-circle', showCloseButton: true },
'Something went wrong.'
)
NotificationService.show(
{ title: 'Uploading', loading: true, dismissAfter: uploadPromise },
'Please wait...'
)
NotificationService.clear()
Theme
import { Use } from '@tempots/dom'
import { Theme, StandaloneAppearanceSelector } from '@tempots/beatui'
StandaloneAppearanceSelector({ size: 'sm' })
Use(Theme, ({ appearance, setAppearancePreference }) => {
return When(
appearance.map(a => a === 'dark'),
() => html.span('Dark mode'),
() => html.span('Light mode'),
)
})
Customizing Design Tokens
beatuiTailwindPlugin({
semanticColors: {
primary: 'emerald',
secondary: 'violet',
},
})
CSS variables available: --color-primary-50 through --color-primary-950, --spacing-*, --radius-*, --shadow-*, --font-size-*, --z-*, --motion-*.
i18n / Locale
import { Use } from '@tempots/dom'
import { Locale, LocaleSelector } from '@tempots/beatui'
LocaleSelector({ size: 'sm' })
Use(Locale, ({ setLocale, locale, direction }) => {
return html.div(locale.map(l => `Current: ${l}`))
})
Common Composition Patterns
Page Layout
import { AppShell, Sidebar, Card } from '@tempots/beatui'
AppShell({
header: { content: MyHeader() },
menu: { width: 240, content: MySidebar() },
main: { content: MyContent() },
})
Responsive Values
const size = prop<ControlSize>('md')
Button({ size, variant: 'filled' }, 'Responsive')
size.set('lg')
Loading States
import { Skeleton, LoadingOverlay } from '@tempots/beatui'
Skeleton({ width: '100%', height: '2rem', animate: true })
LoadingOverlay({ visible: isLoading }, MyContent())
Empty States
import { EmptyState } from '@tempots/beatui'
EmptyState({
icon: 'lucide:inbox',
title: 'No results',
description: 'Try adjusting your search criteria.',
action: Button({ onClick: resetSearch }, 'Clear filters'),
})