@nerds-with-charisma/revit-ui
v0.4.2
Published
Revit-themed shadcn/Tailwind v4 component library
Downloads
944
Readme
@nerds-with-charisma/revit-ui
Revit-themed shadcn / Tailwind v4 primitives. One token object restyles the kit.
Install
pnpm add @nerds-with-charisma/revit-uiPeer: React 19.
Documentation
Browse components locally:
pnpm install
pnpm devOpens Storybook at http://localhost:6006 with live examples, props tables, and usage notes.
Build static docs: pnpm build-storybook → storybook-static/
Use
import { Button, RevitUIProvider, revitTokens } from '@nerds-with-charisma/revit-ui'
import '@nerds-with-charisma/revit-ui/styles.css'
export const App = () => {
return (
<RevitUIProvider tokens={revitTokens}>
<Button>Publish</Button>
</RevitUIProvider>
)
}Pass a custom token object to RevitUIProvider to restyle without forking components. Omit tokens and the kit uses revitTokens. The object must be complete (no merge / partials):
import type { RevitThemeTokens } from '@nerds-with-charisma/revit-ui'
const myTokens: RevitThemeTokens = {
colors: {
background: string
foreground: string
card: string
cardForeground: string
popover: string
popoverForeground: string
primary: string
primaryForeground: string
secondary: string
secondaryForeground: string
muted: string
mutedForeground: string
accent: string
accentForeground: string
destructive: string
destructiveForeground: string
border: string
input: string
ring: string
brand: {
yellow: string
yellowLight: string
offBlack: string
pink: string
purple: string
blue: string
gold: string
brown: string
umber: string
mist: string
}
}
colorsDark: {
background: string
foreground: string
card: string
cardForeground: string
popover: string
popoverForeground: string
primary: string
primaryForeground: string
secondary: string
secondaryForeground: string
muted: string
mutedForeground: string
accent: string
accentForeground: string
destructive: string
destructiveForeground: string
border: string
input: string
ring: string
brand: {
yellow: string
yellowLight: string
offBlack: string
pink: string
purple: string
blue: string
gold: string
brown: string
umber: string
mist: string
}
}
radius: {
sm: string
md: string
lg: string
xl: string
pill: string
}
fontFamily: {
sans: string
}
}
<RevitUIProvider tokens={myTokens}>{children}</RevitUIProvider>Tokens only: import { revitTokens } from '@nerds-with-charisma/revit-ui/theme/revit'
React Native
Expo / React Native apps import from a separate entry — web bundles stay unchanged.
import {
Button,
Card,
CardContent,
CardHeader,
CardTitle,
Input,
Label,
RevitThemeProvider,
} from '@nerds-with-charisma/revit-ui/native'
export const SignInScreen = () => (
<RevitThemeProvider>
<Card variant="elevated">
<CardHeader>
<CardTitle>Sign in</CardTitle>
</CardHeader>
<CardContent>
<Label>Email</Label>
<Input placeholder="[email protected]" autoCapitalize="none" />
<Button onPress={() => {}}>Continue</Button>
</CardContent>
</Card>
</RevitThemeProvider>
)Theme helpers only:
import { tokensToStyleSheet, useRevitTheme } from '@nerds-with-charisma/revit-ui/native/theme'
import { revitTokens } from '@nerds-with-charisma/revit-ui/theme/revit'Metro (Expo)
Enable package exports and the react-native condition so ./native and ./theme/revit resolve:
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
config.resolver.unstable_enablePackageExports = true
config.resolver.unstable_conditionNames = ['react-native', 'require', 'import']
module.exports = configImport paths
| Path | Use |
|------|-----|
| @nerds-with-charisma/revit-ui | Web — Radix + Tailwind components + styles.css |
| @nerds-with-charisma/revit-ui/theme/revit | Shared tokens (web + native) |
| @nerds-with-charisma/revit-ui/native | React Native components (Phase 1) |
| @nerds-with-charisma/revit-ui/native/theme | RevitThemeProvider, useRevitTheme, tokensToStyleSheet |
Peers: react + react-native (no react-dom required for native).
Phase 1 native components
Button, Input, Label, Heading, Text, Card (+ CardHeader, CardTitle, CardDescription, CardContent), Alert (+ AlertTitle, AlertDescription), Tabs (+ TabsList, TabsTrigger).
Browse them in Storybook under Native (rendered via react-native-web in the browser).
Variant names match web CVA (variant="outline", variant="elevated", etc.). Use RN idioms: onPress, style, optional testID (like web data-id).
Web-only (not in native yet)
RevitForm, Select, Dialog, charts, Sonner, and the full Radix primitive set remain web-only. Phase 2 will add native form helpers and Select.
Components
| Category | Components |
|----------|------------|
| Typography | Heading, Text, Link |
| Actions | Button, Badge, Toggle |
| Forms | Field, RevitForm, Input, PasswordInput, Textarea, Checkbox, Switch, Select, RadioGroup, Label |
| Layout | Card, Separator, AspectRatio, ScrollArea |
| Feedback | Alert, Progress, Skeleton, toast / Toaster |
| Overlays | Dialog, Sheet, Popover, Tooltip, DropdownMenu, AlertDialog |
| Navigation | Tabs, Breadcrumb, Pagination, NavigationMenu |
| Revit surfaces | FrostPanel, FrostNav, FrostTile, SearchBar, BrandGradient |
See Storybook for examples. Low-level Form* primitives remain available for custom fields.
Single field
<Field description="We'll never share your email." label="Email">
<Input type="email" placeholder="[email protected]" />
</Field>Low-level escape hatch
Form, FormField, FormItem, FormLabel, FormControl, FormMessage, and useForm are still exported for custom fields that don't fit the config shape.
Forms
Config-driven forms — pass a fields array, RevitForm handles react-hook-form + labels, validation messages, and wiring.
Example (sign-in)
'use client'
import { RevitForm } from '@nerds-with-charisma/revit-ui'
type SignInValues = {
email: string
password: string
remember: boolean
}
export const SignInForm = () => {
return (
<RevitForm<SignInValues>
defaultValues={{ email: '', password: '', remember: false }}
submitLabel="Sign in"
onSubmit={(values) => console.log(values)}
fields={[
{
name: 'email',
label: 'Email',
type: 'input',
placeholder: '[email protected]',
inputProps: { type: 'email', autoComplete: 'email' },
},
{
name: 'password',
label: 'Password',
type: 'password',
placeholder: '••••••••',
showPasswordToggle: true,
forgotPassword: {
label: 'Forgot Password?',
href: '/forgot-password',
},
},
{
name: 'remember',
label: 'Remember me',
type: 'checkbox',
},
]}
/>
)
}Field types
| type | Renders | Notes |
| ------------ | ------------------------------- | ------------------------------------------ |
| input | Input | placeholder, inputProps |
| password | PasswordInput | showPasswordToggle, forgotPassword |
| textarea | Textarea | placeholder, textareaProps |
| checkbox | Checkbox | Inline label |
| switch | Switch | Inline label |
| select | Select | options: { value, label }[] |
| radio | RadioGroup | options: { value, label }[] |
Every field supports optional description (helper text below the control).
RevitForm props
| Prop | Type | Default | Description |
| --------------- | ---------------------------- | ---------- | ------------------------------------ |
| defaultValues | object | required | Initial field values |
| fields | RevitFormField[] | required | Field config array |
| onSubmit | (values) => void | required | Called with validated values |
| submitLabel | string | "Submit" | Submit button text |
| hideSubmit | boolean | false | Hide the built-in submit button |
| resolver | RHF Resolver | — | Pass a zod/yup resolver for validation |
| className | string | — | Classes on the <form> element |
| fieldClassName| string | — | Classes on each FormItem |
Password field extras
{
name: 'password',
label: 'Password',
type: 'password',
showPasswordToggle: true, // default true — eye icon inside the input
forgotPassword: {
label: 'Forgot Password?', // default
href: '/forgot-password', // renders <a> (top-right, above input)
// onClick: () => {}, // use instead of href for a <button>
},
}PasswordInput is also exported standalone if you need it outside RevitForm.
Validation (optional)
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
<RevitForm
resolver={zodResolver(schema)}
defaultValues={{ email: '', password: '' }}
onSubmit={onSubmit}
fields={[/* ... */]}
/>Low-level escape hatch
Form, FormField, FormItem, FormLabel, FormControl, FormMessage, and useForm are still exported for custom fields that don't fit the config shape.
Publish
pnpm publishIf that version is already on npm, prepublishOnly auto-bumps patch, then typecheck + build + publish. No manual version edit.
