# @halfbyte-labs/maka
HalfByte Design System — Vue 3 component library built with Panda CSS and Ark UI.
## Installation
```bash
npm install @halfbyte-labs/maka
# or
bun add @halfbyte-labs/maka
```
## Setup
```ts
// main.ts
import { createApp } from 'vue'
import '@halfbyte-labs/maka/dist/style.css'
import App from './App.vue'
createApp(App).mount('#app')
```
## Importing Components
```ts
import { Button, Input, Modal, useToast } from '@halfbyte-labs/maka'
```
All components, types, composables, design tokens, and recipes are exported from the package root.
---
## Components
### Button
```vue
```
Props:
- `variant`: `"primary" | "secondary" | "outline" | "ghost" | "danger"` — default `"primary"`
- `size`: `"sm" | "md" | "lg"` — default `"md"`
- `type`: `"button" | "submit" | "reset"` — default `"button"`
- `disabled`: boolean
- `loading`: boolean
Emits: `click`
Slots: `default`
---
### Input
```vue
```
Props:
- `modelValue`: string
- `label`: string
- `placeholder`: string
- `helperText`: string
- `errorMessage`: string
- `variant`: `"outline" | "filled"` — default `"outline"`
- `size`: `"sm" | "md" | "lg"` — default `"md"`
- `type`: string (any HTML input type)
- `disabled`, `readonly`, `required`: boolean
- `id`, `name`: string
Emits: `update:modelValue`, `blur`, `focus`
---
### Textarea
```vue
```
Props: same as Input plus:
- `rows`: number
- `resize`: `"none" | "vertical" | "horizontal" | "both"`
Emits: `update:modelValue`, `blur`, `focus`
---
### Select
```vue
```
Props:
- `modelValue`: string | string[]
- `options`: `SelectOption[]` — `{ value: string, label: string, disabled?: boolean }`
- `label`: string
- `placeholder`: string
- `multiple`: boolean
- `disabled`: boolean
- `name`: string
Emits: `update:modelValue`
---
### Checkbox
```vue
```
Props:
- `modelValue`: boolean
- `defaultChecked`: boolean
- `indeterminate`: boolean
- `label`: string
- `size`: `"sm" | "md" | "lg"`
- `color`: `"blue" | "black" | "lime"`
- `disabled`, `required`: boolean
- `name`, `value`: string
Emits: `update:modelValue`
---
### RadioGroup
```vue
```
Props:
- `modelValue`: string
- `defaultValue`: string
- `options`: `RadioOption[]` — `{ value: string, label: string, disabled?: boolean }`
- `label`: string
- `name`: string
- `orientation`: `"horizontal" | "vertical"` — default `"vertical"`
- `size`: `"sm" | "md" | "lg"`
- `color`: `"blue" | "black" | "lime"`
- `disabled`: boolean
Emits: `update:modelValue`
---
### Switch
```vue
```
Props:
- `modelValue`: boolean
- `defaultChecked`: boolean
- `label`: string
- `labelPosition`: `"right" | "left"` — default `"right"`
- `size`: `"sm" | "md"`
- `color`: `"blue" | "black" | "lime"`
- `disabled`: boolean
- `name`: string
Emits: `update:modelValue`
---
### Toggle
```vue
```
Props:
- `modelValue`: boolean
- `variant`: `"default" | "outline"`
- `size`: `"sm" | "md" | "lg"`
- `disabled`: boolean
- `ariaLabel`: string
Emits: `update:modelValue`
Slots: `default`
---
### ToggleGroup
```vue
```
Props:
- `modelValue`: string | string[]
- `items`: `ToggleGroupItem[]` — `{ value: string, label?: string, disabled?: boolean }`
- `multiple`: boolean
- `orientation`: `"horizontal" | "vertical"`
- `size`: `"sm" | "md" | "lg"`
- `disabled`: boolean
Emits: `update:modelValue`
Slots: `item.value` (named, receives `{ item }`)
---
### Label
```vue
```
Props:
- `for`: string
- `size`: `"sm" | "md" | "lg"`
- `required`, `disabled`: boolean
Slots: `default`
---
### NumberInput
```vue
```
Props:
- `modelValue`: number
- `label`: string
- `min`, `max`, `step`: number
- `placeholder`: string
- `disabled`: boolean
- `name`: string
- `formatOptions`: `Intl.NumberFormatOptions`
Emits: `update:modelValue`
---
### Slider
```vue
```
Props:
- `modelValue`: `number | [number, number]`
- `defaultValue`: `number | [number, number]`
- `min`, `max`, `step`: number
- `size`: `"sm" | "md" | "lg"`
- `color`: `"blue" | "black" | "lime"`
- `tone`: `"default" | "error"`
- `label`: string
- `showValue`: boolean
- `formatValue`: `(v: number) => string`
- `minLabel`, `maxLabel`: string
- `showTicks`: boolean
- `withInput`: boolean
- `disabled`: boolean
- `name`: string
Emits: `update:modelValue`
---
### PinInput
```vue
```
Props:
- `modelValue`: string[]
- `label`: string
- `length`: number — default `4`
- `type`: `"numeric" | "alphabetic" | "alphanumeric"`
- `mask`: boolean
- `otp`: boolean
- `autoFocus`: boolean
- `placeholder`: string
- `size`: `"sm" | "md" | "lg"`
- `tone`: `"default" | "error" | "success"`
- `separator`: string
- `disabled`, `invalid`: boolean
- `name`: string
Emits: `update:modelValue`, `complete`
---
### TagsInput
```vue
```
Props:
- `modelValue`: string[]
- `label`: string
- `placeholder`: string
- `max`: number
- `delimiter`: string
- `disabled`, `invalid`: boolean
- `name`: string
Emits: `update:modelValue`
---
### Combobox
```vue
```
Props:
- `modelValue`: string | string[]
- `options`: `ComboboxOption[]` — `{ value: string, label: string, disabled?: boolean }`
- `label`: string
- `placeholder`: string
- `multiple`: boolean
- `disabled`: boolean
- `emptyText`: string
Emits: `update:modelValue`
---
### DatePicker
```vue
```
Props:
- `modelValue`: string (ISO date)
- `label`: string
- `placeholder`: string
- `required`, `disabled`: boolean
- `error`: string
- `minDate`, `maxDate`: string (ISO date)
- `locale`: string — default `"pt-BR"`
- `name`, `ariaLabel`: string
Emits: `update:modelValue`
---
### FileUpload
```vue
```
Props:
- `accept`: string (MIME types)
- `maxFiles`: number
- `maxFileSize`: number (bytes)
- `label`, `description`, `triggerLabel`: string
- `disabled`: boolean
- `name`: string
Emits: `change(files: File[])`
---
### Badge
```vue
New
```
Props:
- `variant`: `"default"` (more variants via recipe)
Slots: `default`
---
### Alert
```vue
Your changes were saved.
```
Props:
- `tone`: `"info" | "success" | "warn" | "error" | "neutral"` — default `"info"`
- `variant`: `"inline" | "banner" | "solid"` — default `"inline"`
- `size`: `"sm" | "md" | "lg"`
- `title`: string
- `hideIcon`: boolean
- `dismissible`: boolean
- `role`: `"alert" | "status"`
- `actions`: `AlertAction[]` — `{ label: string, onClick: () => void, variant?: string }`
Emits: `dismiss`
Slots: `default`, `icon`, `{tone}-icon`
---
### Spinner
```vue
```
Props:
- `size`: `"xs" | "sm" | "md" | "lg" | "xl"`
- `color`: `"blue" | "black" | "lime"`
- `label`: string (visually hidden, for a11y)
---
### Progress
```vue
```
Props:
- `value`: number (omit for indeterminate)
- `min`, `max`: number — defaults `0`, `100`
- `indeterminate`: boolean
- `size`: `"sm" | "md" | "lg"`
- `color`: `"blue" | "black" | "lime"`
- `tone`: `"default" | "success" | "warn" | "error"`
- `striped`: boolean
- `label`: string
- `showValue`: boolean
- `formatValue`: `(v: number, max: number) => string`
- `caption`: `{ left?: string, right?: string }`
- `disabled`: boolean
---
### Skeleton
```vue
```
Props:
- `variant`: `"rect" | "text"` — default `"rect"`
- `width`: string
- `height`: string
- `lines`: number (for `text` variant)
---
### EmptyState
```vue
```
Props:
- `title`: string
- `description`: string
- `bordered`: boolean
Slots: `icon`, `default`, `actions`
---
### Modal
```vue
Are you sure?
```
Props:
- `modelValue`: boolean
- `title`: string
- `description`: string
- `size`: `"sm" | "md" | "lg"` — default `"md"`
- `closable`: boolean — default `true`
Emits: `update:modelValue`
Slots: `header`, `default`, `footer`
---
### Drawer
```vue
```
Props: same as Modal
Slots: `header`, `default`, `footer`
---
### Sheet
```vue
```
Props: same as Modal plus:
- `placement`: `"top" | "bottom" | "left" | "right"` — default `"right"`
Slots: `header`, `default`, `footer`
---
### AlertDialog
```vue
```
Props:
- `modelValue`: boolean
- `title`, `description`: string
- `confirmLabel`, `cancelLabel`: string
- `status`: `"danger" | "warning" | "info"` — default `"danger"`
- `loading`: boolean
Emits: `update:modelValue`, `confirm`, `cancel`
Slots: `footer`, `default`
---
### Tooltip
```vue
```
Props:
- `content`: string
- `placement`: `"top" | "bottom" | "left" | "right"` — default `"top"`
- `openDelay`, `closeDelay`: number (ms)
- `disabled`: boolean
Slots: `default` (trigger)
---
### Popover
```vue
```
Props:
- `title`, `description`: string
- `placement`: Ark UI placement string
- `closable`: boolean
- `open`: boolean
Emits: `update:open`
Slots: `trigger`, `default`
---
### HoverCard
```vue
@username
```
Props:
- `openDelay`, `closeDelay`: number (ms)
- `placement`: Ark UI placement string
- `open`: boolean
Emits: `update:open`
Slots: `trigger`, `default`
---
### Menu
```vue
```
Props:
- `items`: `MenuItem[]` — `{ value: string, label: string, disabled?: boolean, danger?: boolean }`
- `groups`: `MenuGroup[]` — `{ label?: string, items: MenuItem[] }`
- `placement`: Ark UI placement string
- `disabled`: boolean
Emits: `select(value: string)`
Slots: `trigger`, `icon-{value}`, `suffix-{value}`
---
### ContextMenu
```vue
Right-click me
```
Props: same as Menu (items/groups)
Emits: `select(value: string)`
Slots: `default` (target), `content`, `icon-{value}`
---
### Command
```vue
```
Props:
- `modelValue`: string
- `items`: `CommandItem[]` — `{ value: string, label: string, description?: string, group?: string, disabled?: boolean }`
- `placeholder`: string
- `emptyText`: string
Emits: `update:modelValue`, `select(value: string)`
Slots: `icon-{value}`, `suffix-{value}`
---
### Card
```vue
Content
```
Props:
- `variant`: `"default"` (extendable via recipe)
- `noPadding`: boolean
Slots: `default`
---
### Avatar
```vue
```
Props:
- `src`: string (image URL)
- `name`: string (used for initials fallback)
- `alt`: string
- `size`: `"sm" | "md" | "lg" | "xl"`
Slots: `default` (custom fallback)
---
### Tabs
```vue
Content A
Content B
```
Props:
- `modelValue`: string
- `defaultValue`: string
- `tabs`: `TabItem[]` — `{ value: string, label: string, disabled?: boolean }`
Emits: `update:modelValue`
Slots: named by `tab.value`
---
### Accordion
```vue
Answer content here.
```
Props:
- `modelValue`: string | string[]
- `items`: `AccordionItemDef[]` — `{ value: string, label: string, disabled?: boolean }`
- `multiple`: boolean
- `collapsible`: boolean
Emits: `update:modelValue`
Slots: named by `item.value`
---
### Table
```vue
```
Props:
- `columns`: `TableColumn[]` — `{ key: string, label: string, width?: string, align?: 'left' | 'center' | 'right' }`
- `rows`: `TableRow[]` — `Record[]`
- `caption`: string
- `bordered`, `striped`: boolean
Slots: `head-{column.key}`, `cell-{column.key}` (receives `{ row, column }`), `foot`, `body`
---
### Stepper
```vue
Step 1 content
Step 2 content
```
Props:
- `modelValue`: string
- `steps`: `StepperStep[]` — `{ value: string, title: string, description?: string }`
- `linear`: boolean
Emits: `update:modelValue`
Slots: `step-{index}`, `completed`, `prev-trigger`, `next-trigger`, `actions`
---
### Pagination
```vue
```
Props:
- `modelValue`: number (current page, 1-based)
- `total`: number (total items)
- `siblings`: number — default `1`
Emits: `update:modelValue`
---
### Breadcrumb
```vue
```
Props:
- `items`: `BreadcrumbItem[]` — `{ label: string, href?: string }`
- `separator`: string
Slots: `separator`, `item-{index}`
---
### Divider
```vue
```
Props:
- `orientation`: `"horizontal" | "vertical"` — default `"horizontal"`
- `label`: string
Slots: `default`
---
### Typography
```vue
Heading 1
Body text
Muted text
```
Props:
- `variant`: `"h1" | "h2" | "h3" | "h4" | "p" | "lead" | "large" | "small" | "muted" | "code" | "blockquote"`
- `as`: string (override HTML tag)
Slots: `default`
---
### Carousel
```vue
```
Props:
- `slideCount`: number
- `loop`: boolean
- `autoplay`: boolean
- `slidesPerView`: number — default `1`
- `spacing`: number
- `showIndicators`, `showControls`: boolean
Emits: `pageChange(index: number)`
Slots: `slide-{index}`, `default`
---
### Collapsible
```vue
Custom trigger
Hidden content
```
Props:
- `open`: boolean
- `defaultOpen`: boolean
- `disabled`: boolean
- `title`: string
Emits: `update:open`
Slots: `trigger`, `default`
---
### Calendar
```vue
```
Props:
- `modelValue`: string | string[] (ISO dates)
- `mode`: `"single" | "range" | "multi"` — default `"single"`
- `minDate`, `maxDate`: string (ISO date)
- `disabledDates`: string[]
- `size`: `"sm" | "md" | "lg"`
- `color`: `"blue" | "lime" | "black"`
- `dark`: boolean
- `locale`: string — default `"pt-BR"`
- `numberOfMonths`: number
- `showOutsideDays`: boolean
- `events`: string[] (ISO dates with dot indicator)
- `presets`: `CalendarPreset[]` — `{ label: string, getValue: () => string | string[] }`
- `ariaLabel`: string
Emits: `update:modelValue`, `update:month`
---
### RangeCalendar
```vue
```
Props:
- `modelValue`: `{ start?: string, end?: string }`
- `min`, `max`: string (ISO date)
- `locale`: string
- `disabled`: boolean
Emits: `update:modelValue`
---
### ScrollArea
```vue
```
Props:
- `axis`: `"x" | "y"` — default `"y"`
- `height`: string
- `maxHeight`: string
Slots: `default`
---
### NavigationMenu
```vue
```
Props:
- `items`: `NavigationMenuItem[]` — `{ value: string, label: string, href?: string, children?: NavigationMenuLink[] }`
- `NavigationMenuLink`: `{ label: string, href: string }`
---
### Menubar
```vue
```
Props:
- `menus`: `MenubarMenu[]` — `{ value: string, label: string, items?: MenuItem[], groups?: MenuGroup[] }`
Emits: `select(menu: string, value: string)`
---
### Sidebar
```vue
```
Props:
- `modelValue`: boolean
- `side`: `"left" | "right"` — default `"left"`
- `width`: string
- `collapsible`: boolean
Emits: `update:modelValue`
Slots: `sidebar`, `default`
---
### Resizable
```vue
Sidebar
Content
```
Props:
- `panels`: `ResizablePanel[]` — `{ id: string, defaultSize?: number, minSize?: number, maxSize?: number }`
- `orientation`: `"horizontal" | "vertical"` — default `"horizontal"`
Emits: `resize(sizes: Record)`
Slots: named by `panel.id`
---
### AspectRatio
```vue
```
Props:
- `ratio`: number — default `16/9`
Slots: `default`
---
### Kbd
```vue
⌘K
```
Props:
- `size`: `"sm" | "md" | "lg"`
Slots: `default`
---
### Toast System
Register `` once in your app layout, then use `useToast()` anywhere.
```vue
```
```ts
// Anywhere in a component
import { useToast } from '@halfbyte-labs/maka'
const toast = useToast()
toast.success('Saved!', 'Your changes were saved.')
toast.error('Error', 'Something went wrong.')
toast.info('Info', 'New update available.')
toast.warning('Warning', 'You are running low on storage.')
// Full options
toast.show({
tone: 'success',
title: 'Done',
description: 'Task completed.',
duration: 5000,
actions: [{ label: 'Undo', onClick: () => revert() }],
})
toast.dismiss(id)
toast.dismissAll()
```
Toaster props:
- `placement`: `"top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end"` — default `"bottom-end"`
---
## Design Tokens
Exported as `halfbyteTokens` (type: `HalfbyteTokens`). Used internally by Panda CSS recipes.
### Colors
| Token | Value |
|-------|-------|
| white | #efe8ef |
| black | #111114 |
| blue | #3381ff |
| lime | #a6d31c |
| gray | #878489 |
| cyan | #92d5ef |
| blueLight | #92acff |
| blueDark | #0d3aa0 |
| limeLight | #c6f995 |
| limeDark | #5ba323 |
### Semantic Colors
| Token | Value |
|-------|-------|
| bg | #efe8ef |
| bg-inverse | #111114 |
| bg-elevated | #ffffff |
| bg-muted | #e3dde3 |
| fg | #111114 |
| fg-inverse | #efe8ef |
| fg-muted | #878489 |
| fg-subtle | #5a585c |
| accent | #3381ff |
| detail | #a6d31c |
### Typography
Fonts:
- Display: `'Space Grotesk'`
- Body: `'Plus Jakarta Sans'`
- Mono: `'JetBrains Mono'`
Sizes: `xs` (12px) · `sm` (14px) · `base` (16px) · `md` (18px) · `lg` (22px) · `xl` (28px) · `2xl` (36px) · `3xl` (48px) · `4xl` (64px) · `5xl` (88px)
Weights: `regular` (400) · `medium` (500) · `semibold` (600) · `bold` (700) · `xbold` (800)
Line heights: `tight` (1.05) · `snug` (1.2) · `normal` (1.45) · `relaxed` (1.65)
### Spacing
`1` = 4px · `2` = 8px · `3` = 12px · `4` = 16px · `6` = 24px · `8` = 32px · `12` = 48px · `16` = 64px · `24` = 96px
### Border Radius
`none` (0) · `sm` (2px) · `md` (4px) · `lg` (8px) · `pill` (999px)
### Border Width
`hair` (1px) · `strong` (1.5px) · `thick` (2px)
### Shadows
`sm` · `md` · `lg`
### Z-Index
`hide` (-1) · `base` (0) · `raised` (10) · `dropdown` (100) · `sticky` (200) · `overlay` (300) · `modal` (400) · `toast` (500)
---
## Panda CSS Integration
If your project uses Panda CSS, you can import individual recipes to extend or override styles:
```ts
import { buttonRecipe } from '@halfbyte-labs/maka'
import { halfbyteTokens } from '@halfbyte-labs/maka'
```
The `styled-system/` directory ships with the package and provides full type-safe utility classes.
---
## TypeScript
All component prop types are exported:
```ts
import type {
ButtonProps,
InputProps,
SelectOption,
MenuItem,
MenuGroup,
TableColumn,
TableRow,
AccordionItemDef,
TabItem,
StepperStep,
RadioOption,
ComboboxOption,
CommandItem,
ToggleGroupItem,
NavigationMenuItem,
MenubarMenu,
ResizablePanel,
AlertAction,
CalendarPreset,
ToastOptions,
HalfbyteTokens,
} from '@halfbyte-labs/maka'
```
---
## Usage with Astro
```ts
// astro.config.mjs
import { defineConfig } from 'astro/config'
import vue from '@astrojs/vue'
export default defineConfig({
integrations: [vue()],
})
```
```astro
---
// Layout.astro
import '@halfbyte-labs/maka/dist/style.css'
---
```
```vue
```
```astro
---
import MyComponent from './MyComponent.vue'
---
```
---
## Peer Dependencies
- `vue` ^3.5.33
- `typescript` ^5
## Dependencies
- `@ark-ui/vue` (accessible UI primitives)
- `@internationalized/date` (date handling for Calendar/DatePicker)