🚧 VeloxKit is pre-release software. APIs may change before v1.0. Get started →
Documentation
Packages
@velox/design

@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 componentsButton, 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

PropTypeDefaultDescription
colorScheme'light' | 'dark' | 'system''system'Color scheme to use. 'system' follows the OS preference and polls for changes every 2 s.
overridesPartial<Tokens>Token overrides merged on top of the selected scheme
childrenReactNode
// 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

TokenLightDarkUsage
colors.bg#eff1f5#1e1e2eRoot window background
colors.surface#e6e9ef#181825Cards, panels
colors.surfaceRaised#dce0e8#11111bElevated elements
colors.text#4c4f69#cdd6f4Primary body text
colors.textMuted#6c6f85#a6adc8Secondary / caption text
colors.textDisabled#7c7f93#6c7086Disabled labels
colors.border#acb0be#585b70Dividers, input outlines
colors.borderFocus#1e66f5#89b4faFocused input ring
colors.primary#1e66f5#89b4faButtons, links
colors.primaryText#eff1f5#1e1e2eText on primary backgrounds
colors.primaryHover#209fb5#74c7ecHover state on primary
colors.secondary#ccd0da#313244Secondary button background
colors.secondaryText#4c4f69#cdd6f4Text on secondary backgrounds
colors.error#d20f39#f38ba8Error states
colors.errorSurface#fde8ecError background tint
colors.success#40a02b#a6e3a1Success states
colors.successSurface#e6f9e6Success background tint
colors.warning#df8e1d#f9e2afWarning / caution states
colors.warningSurface#fef9e8Warning background tint
colors.scrimrgba(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] = 64

Typography

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 shape

Base 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:

PropTypeDefaultDescription
labelstringButton text
onPress() => voidPress handler
variant'primary' | 'secondary' | 'ghost''primary'Visual style
disabledbooleanfalseDisables interaction and grays out
styleViewStyleContainer 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:

PropTypeDefaultDescription
childrenReactNodeCard content
paddingnumberspace[4] (16px)Inner padding
styleViewStyleStyle 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:

PropTypeDefaultDescription
size'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl''base'Font size token key
mutedbooleanfalseUses colors.textMuted
boldbooleanfalseUses fontWeight.semibold
styleTextStyleAdditional 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:

PropTypeDefaultDescription
labelstringBadge text
variant'default' | 'success' | 'warning' | 'error''default'Color intent
styleViewStyleContainer 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>
  )
}