@velox/design Stable
A complete design system for VeloxKit apps. Ships with:
- Design tokens — Catppuccin Latte (light) and Mocha (dark) semantic color palettes, 8-point spacing scale, type scale, border-radius scale
- ThemeProvider — wraps your app with a light/dark/system-aware context
- useTheme() — hook to read current tokens in any component
- createTheme() — build fully custom themes by overriding any token
- Base components —
Button,Card,Divider,Label,Heading,Badge
Install
bun add @velox/design@velox/design is included automatically in projects created with velox create --template notes|dashboard|settings.
Quick start
// index.tsx
import { render } from 'veloxkit'
import { ThemeProvider } from '@velox/design'
import App from './App'
render(
<ThemeProvider colorScheme="system">
<App />
</ThemeProvider>
)// App.tsx
import { Button, Card, Heading, Label } from '@velox/design'
export default function App() {
return (
<Card>
<Heading level={2}>Hello, VeloxKit</Heading>
<Label muted>Build native apps with React.</Label>
<Button label="Get started" onPress={() => {}} />
</Card>
)
}ThemeProvider
Provides design tokens to all descendants via React Context.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
colorScheme | 'light' | 'dark' | 'system' | 'system' | Color scheme to use. 'system' follows the OS preference and polls for changes every 2 s. |
overrides | Partial<Tokens> | — | Token overrides merged on top of the selected scheme |
children | ReactNode | — | — |
// Lock to dark mode
<ThemeProvider colorScheme="dark">
<App />
</ThemeProvider>
// Follow OS preference (default)
<ThemeProvider colorScheme="system">
<App />
</ThemeProvider>
// Override the primary accent
<ThemeProvider overrides={{ colors: { primary: '#ff6b6b' } }}>
<App />
</ThemeProvider>useTheme()
Returns the current token set. Must be called inside a <ThemeProvider>.
import { useTheme } from '@velox/design'
function StatusBadge({ ok }) {
const { colors, space, radius, fontSize } = useTheme()
return (
<View style={{
backgroundColor: ok ? colors.successSurface : colors.errorSurface,
paddingHorizontal: space[3],
paddingVertical: space[1],
borderRadius: radius.full,
}}>
<Text style={{ color: ok ? colors.success : colors.error, fontSize: fontSize.sm }}>
{ok ? 'Online' : 'Offline'}
</Text>
</View>
)
}createTheme(base, overrides)
Build a custom theme object for use with the overrides prop.
import { createTheme } from '@velox/design'
// Purple-accented dark theme
const purpleDark = createTheme('dark', {
colors: {
primary: '#9d5bd2',
primaryText: '#ffffff',
primaryHover: '#b06ee8',
},
})
// Use it:
<ThemeProvider colorScheme="dark" overrides={purpleDark}>
<App />
</ThemeProvider>Design tokens
All token values are available via tokens (light) and darkTokens (dark) exports,
or via useTheme() inside a component.
Colors
| Token | Light | Dark | Usage |
|---|---|---|---|
colors.bg | #eff1f5 | #1e1e2e | Root window background |
colors.surface | #e6e9ef | #181825 | Cards, panels |
colors.surfaceRaised | #dce0e8 | #11111b | Elevated elements |
colors.text | #4c4f69 | #cdd6f4 | Primary body text |
colors.textMuted | #6c6f85 | #a6adc8 | Secondary / caption text |
colors.textDisabled | #7c7f93 | #6c7086 | Disabled labels |
colors.border | #acb0be | #585b70 | Dividers, input outlines |
colors.borderFocus | #1e66f5 | #89b4fa | Focused input ring |
colors.primary | #1e66f5 | #89b4fa | Buttons, links |
colors.primaryText | #eff1f5 | #1e1e2e | Text on primary backgrounds |
colors.primaryHover | #209fb5 | #74c7ec | Hover state on primary |
colors.secondary | #ccd0da | #313244 | Secondary button background |
colors.secondaryText | #4c4f69 | #cdd6f4 | Text on secondary backgrounds |
colors.error | #d20f39 | #f38ba8 | Error states |
colors.errorSurface | #fde8ec | — | Error background tint |
colors.success | #40a02b | #a6e3a1 | Success states |
colors.successSurface | #e6f9e6 | — | Success background tint |
colors.warning | #df8e1d | #f9e2af | Warning / caution states |
colors.warningSurface | #fef9e8 | — | Warning background tint |
colors.scrim | rgba(76,79,105,0.4) | rgba(0,0,0,0.5) | Modal overlay |
Spacing (8-point grid)
space[0] = 0
space[1] = 4
space[2] = 8
space[3] = 12
space[4] = 16
space[5] = 20
space[6] = 24
space[8] = 32
space[10] = 40
space[12] = 48
space[16] = 64Typography
fontSize.xs = 11
fontSize.sm = 13
fontSize.base = 15
fontSize.md = 17
fontSize.lg = 20
fontSize.xl = 24
fontSize['2xl'] = 30
fontSize['3xl'] = 36
fontSize['4xl'] = 48
fontWeight.regular = '400'
fontWeight.medium = '500'
fontWeight.semibold = '600'
fontWeight.bold = '700'Border radius
radius.none = 0
radius.sm = 4
radius.md = 8
radius.lg = 12
radius.xl = 16
radius.full = 9999 // pill shapeBase components
All base components read the active theme from useTheme() automatically.
Button
import { Button } from '@velox/design'
<Button label="Save" onPress={handleSave} />
<Button label="Cancel" variant="secondary" onPress={handleCancel} />
<Button label="Delete" variant="ghost" onPress={handleDelete} />
<Button label="Loading…" disabled />Props:
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Button text |
onPress | () => void | — | Press handler |
variant | 'primary' | 'secondary' | 'ghost' | 'primary' | Visual style |
disabled | boolean | false | Disables interaction and grays out |
style | ViewStyle | — | Container style override |
Card
<Card>
<Heading level={3}>Title</Heading>
<Label>Some description text here.</Label>
</Card>
<Card padding={24} style={{ marginBottom: 12 }}>
<Label bold>Custom padding</Label>
</Card>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Card content |
padding | number | space[4] (16px) | Inner padding |
style | ViewStyle | — | Style override |
Divider
<Divider /> // horizontal
<Divider direction="vertical" /> // vertical (requires fixed-height parent)Props: direction?: 'horizontal' | 'vertical', style?
Label
<Label>Body text</Label>
<Label muted>Secondary caption</Label>
<Label bold size="sm">Small bold text</Label>
<Label size="xl">Large text</Label>Props:
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | 'base' | Font size token key |
muted | boolean | false | Uses colors.textMuted |
bold | boolean | false | Uses fontWeight.semibold |
style | TextStyle | — | Additional style |
Heading
<Heading level={1}>App Title</Heading>
<Heading level={2}>Section</Heading>
<Heading level={3}>Subsection</Heading>
<Heading level={4}>Group</Heading>Props: level?: 1 | 2 | 3 | 4 (maps to 3xl/2xl/xl/lg font size), style?
Badge
<Badge label="Stable" />
<Badge label="Connected" variant="success" />
<Badge label="Syncing" variant="warning" />
<Badge label="Error" variant="error" />Props:
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Badge text |
variant | 'default' | 'success' | 'warning' | 'error' | 'default' | Color intent |
style | ViewStyle | — | Container style override |
Full settings page example
import {
ThemeProvider, useTheme, Button, Card, Divider,
Label, Heading, Badge
} from '@velox/design'
import { Switch } from 'veloxkit'
import { useState } from 'react'
function SettingsPage() {
const { colors, space } = useTheme()
const [notifications, setNotifications] = useState(true)
const [analytics, setAnalytics] = useState(false)
return (
<ScrollView style={{ backgroundColor: colors.bg, padding: space[4] }}>
<Heading level={2}>Settings</Heading>
<Card style={{ marginTop: space[4] }}>
<Heading level={4}>Notifications</Heading>
<Divider style={{ marginVertical: space[3] }} />
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<View>
<Label bold>Push notifications</Label>
<Label muted size="sm">Alerts for new activity</Label>
</View>
<Switch value={notifications} onValueChange={setNotifications} />
</View>
</Card>
<Card style={{ marginTop: space[3] }}>
<Heading level={4}>Privacy</Heading>
<Divider style={{ marginVertical: space[3] }} />
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1 }}>
<View style={{ flexDirection: 'row', gap: space[2], alignItems: 'center' }}>
<Label bold>Analytics</Label>
<Badge label="Optional" variant="default" />
</View>
<Label muted size="sm">Anonymous usage data to improve the app</Label>
</View>
<Switch value={analytics} onValueChange={setAnalytics} />
</View>
</Card>
<Button
label="Save changes"
onPress={() => {}}
style={{ marginTop: space[6] }}
/>
</ScrollView>
)
}
export default function App() {
return (
<ThemeProvider colorScheme="system">
<SettingsPage />
</ThemeProvider>
)
}