Skip to content

Components

Import from @kinbot/components after adding it to your app.json dependencies:

{
"dependencies": {
"@kinbot/components": "/api/mini-apps/sdk/kinbot-components.js"
}
}

All components auto-adapt to light/dark theme.

Flexbox container.

<Stack direction="row" gap="8px" align="center" justify="between" wrap>
<Button>A</Button>
<Button>B</Button>
</Stack>
PropTypeDefault
direction"row" | "column""column"
gapstring
alignCSS alignItems
justifyCSS justifyContent
wrapbooleanfalse

CSS Grid with auto-fit support.

<Grid columns={3} gap="16px">
<Grid.Item colSpan={2}>Wide</Grid.Item>
<Grid.Item>Normal</Grid.Item>
</Grid>
{/* Responsive auto-fit */}
<Grid minChildWidth="250px" gap="16px">
<Card>...</Card>
<Card>...</Card>
</Grid>
<Divider orientation="horizontal" />
<Card hover>
<Card.Header>
<Card.Title>Title</Card.Title>
<Card.Description>Subtitle</Card.Description>
</Card.Header>
<Card.Content>Body</Card.Content>
<Card.Footer>
<Button>Action</Button>
</Card.Footer>
</Card>

Collapsible panel with title bar.

<Panel title="Settings" icon="⚙️" collapsible defaultOpen actions={<Button size="sm">Reset</Button>} variant="outlined">
Content here
</Panel>

Variants: default, outlined, filled.

<Button variant="primary" size="md" loading={false}>Click me</Button>
<ButtonGroup>
<Button>Left</Button>
<Button>Right</Button>
</ButtonGroup>

Variants: primary, secondary, outline, ghost, danger, success, warning. Sizes: sm, md, lg.

All support label and error props for form validation.

<Input label="Name" error={errors.name} value={name} onChange={e => setName(e.target.value)} />
<Textarea label="Bio" rows={4} />
<Select label="Country" options={[{value: "fr", label: "France"}]} />
<Checkbox label="Accept terms" checked={accepted} onChange={e => setAccepted(e.target.checked)} />
<Switch label="Dark mode" checked={dark} onChange={setDark} />
<RadioGroup
label="Size"
options={[{value: "s", label: "Small"}, {value: "m", label: "Medium"}, {value: "l", label: "Large"}]}
value={size}
onChange={setSize}
direction="row"
/>
<Slider label="Volume" value={vol} onChange={setVol} min={0} max={100} showValue formatValue={v => `${v}%`} />
<DatePicker label="Date" type="date" value={date} onChange={setDate} min="2024-01-01" />

Types: date, datetime-local, time.

Numeric stepper with +/- buttons.

<NumberInput label="Quantity" value={qty} onChange={setQty} min={1} max={99} step={1} size="md" />

Searchable select dropdown with keyboard navigation.

<Combobox
label="Country"
options={[{value: "fr", label: "France", icon: "🇫🇷"}]}
value={country}
onChange={setCountry}
clearable
placeholder="Search..."
/>

Multi-tag entry field.

<TagInput
label="Tags"
value={tags}
onChange={setTags}
suggestions={["react", "typescript", "css"]}
maxTags={5}
/>

Full color picker with saturation/brightness area, hue slider, and hex input.

<ColorPicker label="Color" value={color} onChange={setColor} swatches={["#ff0000", "#00ff00"]} />

Markdown editor with toolbar, live preview, and split view.

<MarkdownEditor value={md} onChange={setMd} showPreview showToolbar minHeight={200} />

Visual month calendar with single, multiple, or range selection.

<Calendar mode="range" value={range} onChange={setRange} events={[{date: "2024-03-15", color: "red", label: "Deadline"}]} />

Two-input field with Calendar popover. Supports presets.

<DateRangePicker
label="Period"
value={range}
onChange={setRange}
presets={[{label: "Last 7 days", start: "2024-03-01", end: "2024-03-07"}]}
/>

Full form with validation.

<Form onSubmit={handleSubmit} initialValues={{name: "", email: ""}}>
<Form.Field name="name" label="Name" rules={["required", {type: "minLength", value: 2}]}>
<Input />
</Form.Field>
<Form.Field name="email" label="Email" rules={["required", "email"]}>
<Input type="email" />
</Form.Field>
<Form.Actions>
<Form.Reset variant="ghost">Reset</Form.Reset>
<Form.Submit loadingText="Saving...">Save</Form.Submit>
</Form.Actions>
</Form>

Validation rules: "required", "email", {type: "minLength", value, message?}, {type: "maxLength", value}, {type: "min", value}, {type: "max", value}, {type: "pattern", value: /regex/}, {type: "match", value: "fieldName"}, or a custom function (value, allValues) => string | null.

<Badge variant="success">Active</Badge>
<Tag onRemove={() => remove(id)}>React</Tag>

Badge variants: default, success, warning, error, info.

<Stat value="1,234" label="Users" trend="+12%" trendUp />
<Avatar src="/photo.jpg" alt="User" />
<Avatar initials="NV" size={40} />
<AvatarGroup avatars={[{src: "/a.jpg"}, {name: "Bob"}]} max={3} size="md" />
<Tooltip text="More info" position="top">
<Button>Hover me</Button>
</Tooltip>
<ProgressBar value={65} max={100} showLabel />
<Table
columns={[
{key: "name", label: "Name"},
{key: "status", label: "Status", render: (v) => <Badge>{v}</Badge>},
]}
data={items}
onRowClick={(row) => select(row)}
/>

Feature-rich data table with sorting, filtering, search, pagination, and selection.

<DataGrid
columns={[
{key: "name", label: "Name", sortable: true, filterable: true},
{key: "email", label: "Email", sortable: true},
{key: "role", label: "Role", filterable: true},
]}
data={users}
pageSize={10}
pageSizeOptions={[10, 25, 50]}
searchable
selectable
onSelectionChange={setSelected}
striped
/>

Use DataGrid instead of Table + Pagination for data-heavy apps.

<List divided items={[
{primary: "Item 1", secondary: "Description", icon: "📦", action: <Button size="sm">Edit</Button>},
]} />
<Tabs
tabs={[{id: "general", label: "General"}, {id: "advanced", label: "Advanced", badge: 3}]}
active={tab}
onChange={setTab}
/>
<Pagination page={page} totalPages={10} onChange={setPage} />
<Breadcrumbs items={[{label: "Home", onClick: goHome}, {label: "Settings"}]} />
<Modal open={open} onClose={() => setOpen(false)} title="Confirm" size="sm">
<p>Are you sure?</p>
<Button onClick={confirm}>Yes</Button>
</Modal>

Sizes: sm, md, lg, xl.

<Drawer open={open} onClose={close} title="Details" side="right" width="400px">
Content
</Drawer>
<Popover trigger={<Button>Menu</Button>} content={<div>Popover content</div>} placement="bottom" />
<Accordion
items={[{id: "1", title: "Section 1", content: <p>Content</p>}]}
multiple
defaultOpen={["1"]}
/>
<DropdownMenu
trigger={<Button>Actions</Button>}
items={[
{label: "Edit", icon: "✏️", onClick: edit},
{divider: true},
{label: "Delete", danger: true, onClick: del},
]}
/>

All charts use --color-chart-1 through --color-chart-5 CSS variables for theme-aware colors.

<BarChart data={[{label: "Jan", value: 100}, {label: "Feb", value: 150}]} showValues animate />
{/* Single series */}
<LineChart data={[{label: "Mon", value: 10}, {label: "Tue", value: 20}]} showDots curved />
{/* Multi-series */}
<LineChart data={[{label: "Mon", values: [10, 15]}, {label: "Tue", values: [20, 18]}]} series={["Sales", "Returns"]} showArea />
<PieChart data={[{label: "A", value: 30}, {label: "B", value: 70}]} donut showLabels showLegend />
<SparkLine data={[5, 10, 8, 15, 12]} width={100} height={30} showArea />

Multi-step wizards.

<Stepper
steps={[{label: "Account"}, {label: "Profile"}, {label: "Review"}]}
activeStep={step}
onStepClick={setStep}
/>
<StepperContent activeStep={step} animated>
<AccountForm />
<ProfileForm />
<ReviewPage />
</StepperContent>
<FileUpload accept="image/*" multiple maxSize={5_000_000} maxFiles={3} onFiles={handleFiles} onError={alert} />
<CodeBlock code={jsonString} language="json" showCopy showLineNumbers maxHeight="300px" />
<Timeline items={[
{title: "Created", time: "9:00 AM", icon: "🎉", color: "green"},
{title: "Updated", time: "2:00 PM", description: "Changed status"},
]} />

Drag-and-drop kanban board.

<Kanban
columns={[
{id: "todo", title: "To Do", cards: [{id: "1", title: "Task 1", priority: "high", tags: ["bug"]}]},
{id: "done", title: "Done", cards: []},
]}
onChange={setCols}
onCardClick={(card) => openDetail(card)}
allowAddCards
allowEditCards
/>

Hash-based routing for multi-page apps.

import { Router, Route, NavLink, useHashRouter } from "@kinbot/components";
function App() {
return (
<Router>
<nav>
<NavLink to="/" exact>Home</NavLink>
<NavLink to="/settings">Settings</NavLink>
</nav>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Router>
);
}

Components: Router, Route (path + element), Link, NavLink (adds active class), Navigate (redirect). Hook: useHashRouter() returns { path, params, query, navigate }.