- Svelte 79.7%
- TypeScript 11.6%
- CSS 4.1%
- MDX 2.8%
- JavaScript 1.1%
- Other 0.7%
- form-utils.getColSpanClass now returns literal class strings via a
lookup map instead of a template literal. Tailwind's scanner can
detect every possible class (col-span-12 sm:col-span-1..12) at
build time without any consumer-side safelist.
- base.css adds '@source "../**/*.{svelte,ts,js}"' so every consumer
project automatically scans kuku-ui's own source files when building
Tailwind CSS. Consumers no longer need to configure @source or
safelist dynamic classes themselves.
Together these changes remove the entire class of "missing responsive
classes" bugs that consumers hit when kuku-ui generated classes via
template literals.
|
||
|---|---|---|
| .storybook | ||
| docs/superpowers | ||
| src | ||
| static | ||
| .editorconfig | ||
| .gitignore | ||
| .npmrc | ||
| .postcss.config.js | ||
| .prettierignore | ||
| .prettierrc | ||
| CLAUDE.md | ||
| LICENSE | ||
| llms.txt | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| svelte.config.js | ||
| tailwind.config.js | ||
| tsconfig.json | ||
| vite.config.ts | ||
kuku-ui
Beautiful, accessible Svelte 5 + DaisyUI component library with Tailwind CSS 4.
Installation
npm install kuku-ui
Peer Dependencies
Your project needs these installed:
npm install svelte@^5 @sveltejs/kit@^2 tailwindcss@^4 daisyui@^5 yup@^1
npm install @tailwindcss/vite @tailwindcss/forms @tailwindcss/typography
CSS Setup
Add these lines to your main CSS file (e.g. app.css), after @import "tailwindcss":
@import "tailwindcss";
@source "../node_modules/kuku-ui/src/lib/**/*.svelte";
@import "kuku-ui/base.css";
@source— tells Tailwind to scan kuku-ui component templates for utility classes@import "kuku-ui/base.css"— provides shared theme tokens, base styles, and utility overrides that components depend on
If you use the Form component (which generates grid column classes dynamically), also add:
@source inline("sm:col-span-1 sm:col-span-2 sm:col-span-3 sm:col-span-4 sm:col-span-5 sm:col-span-6 sm:col-span-7 sm:col-span-8 sm:col-span-9 sm:col-span-10 sm:col-span-11 sm:col-span-12");
Quick Start
Import components from kuku-ui:
<script lang="ts">
import { Button, Modal, toast, Toaster } from 'kuku-ui';
</script>
<Button look="primary" onclick={() => toast.success('Done', 'It works!')}>
Click me
</Button>
<Toaster />
Components
Buttons
| Component | Description |
|---|---|
Button |
Primary button with variants, loading state, and size options |
ButtonIcon |
Button with icon + text side-by-side |
ButtonLink |
Anchor element styled as a button |
BtnFloat |
Fixed floating action button (bottom-right) |
BtnSwap |
Toggle button with swap animation (sun/moon theme toggle) |
<Button look="primary" size="md" busy={loading} outline>Save</Button>
<Button look="error" size="sm" circle><Cancel /></Button>
<ButtonLink href="/about" look="accent">About</ButtonLink>
Button looks: neutral | primary | secondary | accent | info | success | warning | error | ghost | link
Form System
The Form component renders fields from a schema with built-in Yup validation:
<script lang="ts">
import { Form, strRequired, emailRequired, type FormSchema } from 'kuku-ui';
const schema: FormSchema = [
[
[
{ name: 'name', type: 'text', label: 'Full Name', col: '6', validate: strRequired },
{ name: 'email', type: 'email', label: 'Email', col: '6', validate: emailRequired }
]
],
[
[
{ name: 'bio', type: 'textarea', label: 'Bio', col: '12' }
]
]
];
async function handleSubmit(data: { name: string; email: string; bio: string }) {
await api.save(data);
}
</script>
<Form id="profile" {schema} onSubmit={handleSubmit} submitText="Save Profile" />
Individual Form Components
| Component | Description |
|---|---|
Textbox |
Text/email/password/number input with autocomplete |
Textarea |
Multi-line text input |
Select |
Dropdown with options |
Checkbox |
Checkbox with label |
Radio |
Radio button group |
SearchList |
Async search with single/multi-select and badges |
Phone |
International phone input with country selector and masking |
DateTimePicker |
Calendar picker with optional time selection |
TimePicker |
Time-only picker with minute intervals |
<Textbox name="city" label="City" value={city} onChange={(n, v) => city = v} />
<SearchList
name="tags"
label="Tags"
multiple
onSearch={async (q) => await fetchTags(q)}
bind:value={selectedTags}
/>
<DateTimePicker
name="date"
label="Event Date"
format="YYYY-MM-DD"
showTime
minuteInterval={15}
bind:value={eventDate}
/>
<Phone name="phone" label="Phone" defaultCountry="US" bind:value={phone} />
Validation Helpers
import {
strRequired, strOptional,
emailRequired, emailOptional,
numRequired,
validatePwd, validatePhone,
validateMinLength, validateMaxLength,
validateNumberRange, validateUrl
} from 'kuku-ui';
Modals
<script lang="ts">
import { Modal, ModalWithForm } from 'kuku-ui';
let open = $state(false);
</script>
<!-- Simple modal -->
<Modal bind:open title="Confirm" onOk={handleOk} onCancel={() => open = false}>
<p>Are you sure?</p>
</Modal>
<!-- Modal with integrated form -->
<ModalWithForm
{open}
title="Edit User"
formID="edit-user"
formSchema={schema}
formInitialData={user}
onSubmit={handleSubmit}
onCancel={() => open = false}
/>
Sizes: xs | sm | md | lg | xl
Toast Notifications
<script lang="ts">
import { toast, Toaster } from 'kuku-ui';
</script>
<!-- Place once in your layout -->
<Toaster />
<!-- Trigger from anywhere -->
<button onclick={() => toast.success('Saved', 'Your changes have been saved')}>
Save
</button>
<button onclick={() => toast.error('Error', 'Something went wrong')}>
Error
</button>
Methods: toast.info() | toast.success() | toast.warning() | toast.error()
Each accepts (title, message, clearInSeconds?) where clearInSeconds defaults to 7 (use 0 for persistent).
Alert
<Alert type="warning" title="Heads up">
This action cannot be undone.
</Alert>
Types: info | success | warning | error
ConfirmAction
Wraps any trigger (button, link, toggle) with a confirmation dialog:
<ConfirmAction
title="Delete Item"
onConfirm={async () => await deleteItem(id)}
askForReason
btnLook="error"
>
Delete
</ConfirmAction>
Icons
39 SVG icons with optional outline variants:
<script lang="ts">
import { Calendar, CalendarOutline, Home, Trash, Search } from 'kuku-ui';
import Icon from 'kuku-ui/svg/Icon.svelte';
</script>
<!-- Direct import -->
<Calendar cls="h-5 w-5" />
<!-- Icon wrapper with outline toggle -->
<Icon icon="home" outline />
Available icons: bar3 calendar cancel checkcircle chevron-left chevron-right error eye folder home info moon pencil pencil-square success sun search trash user warning
DataTable
Server-side paginated table with virtual scrolling, sorting, search, row selection, and mobile card view:
<script lang="ts">
import { DataTable, type DataTableColumn, type DataTableFetchParams } from 'kuku-ui';
type User = { id: number; name: string; email: string; role: string };
const columns: DataTableColumn<User>[] = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email', sortable: true },
{ key: 'role', label: 'Role' }
];
async function fetchUsers({ page, pageSize, search, sortKey, sortDir }: DataTableFetchParams) {
const res = await fetch(`/api/users?page=${page}&size=${pageSize}&q=${search ?? ''}`);
return res.json(); // { rows: User[], total: number }
}
</script>
<DataTable {columns} onFetch={fetchUsers} pageSize={50} cls="h-[600px]" />
Features: virtual scroll for 100K+ rows, column sorting, search with debounce, row selection (selectable), custom cell renderers, mobile-responsive card view, toolbar snippet, empty state snippet.
DataList
Simpler alternative to DataTable — renders arbitrary items via a snippet instead of columns. Same virtual scroll + infinite scroll engine:
<script lang="ts">
import { DataList, type DataTableFetchParams } from 'kuku-ui';
type User = { id: number; name: string; email: string };
async function fetchUsers({ page, pageSize, search }: DataTableFetchParams) {
const res = await fetch(`/api/users?page=${page}&size=${pageSize}&q=${search ?? ''}`);
return res.json(); // { rows: User[], total: number }
}
</script>
<DataList fetcher={fetchUsers} pageSize={50} itemHeight={80} cls="h-[600px]">
{#snippet itemSnippet(row: User)}
<div class="flex items-center gap-4 border-b px-4 py-3">
<div class="font-medium">{row.name}</div>
<div class="text-sm text-gray-500">{row.email}</div>
</div>
{/snippet}
</DataList>
Features: virtual scroll for 100K+ rows, infinite scroll pagination, search with debounce, toolbar snippet, empty state snippet, refresh() method.
DataSelect
Versatile select/combobox with search, single/multi-select, grouped options, async search, and creatable items:
<script lang="ts">
import { DataSelect, type Option, type OptionGroup } from 'kuku-ui';
const colors: Option[] = [
{ key: 'red', value: 'Red' },
{ key: 'blue', value: 'Blue' }
];
let selected = $state<Option | undefined>();
let multiSelected = $state<Option[]>([]);
</script>
<!-- Single select with static options -->
<DataSelect name="color" label="Color" options={colors} bind:value={selected} />
<!-- Multi-select with async search and creatable -->
<DataSelect
name="tags"
label="Tags"
multiple
creatable
onSearch={async (q) => await fetchTags(q)}
bind:value={multiSelected}
/>
Features: client-side filtering, async search with debounce, grouped options (OptionGroup[]), multi-select with badge chips, creatable items, custom item/group/create snippets, clearable, keyboard navigation.
Other Components
| Component | Description |
|---|---|
Badge |
Small decorative label with color and size variants |
Portal Action
Teleport DOM elements to a different location:
<script lang="ts">
import { portal, createPortal } from 'kuku-ui';
</script>
<!-- Target location -->
<div use:createPortal={'my-portal'}></div>
<!-- Content will be moved to target -->
<div use:portal={'my-portal'}>
This renders at the target location
</div>
Drag and Drop
Custom drag-and-drop with HTML5 Drag API and Pointer Events:
<script lang="ts">
import { draggable } from 'kuku-ui/drag-and-drop/action-dragable';
import { droppable } from 'kuku-ui/drag-and-drop/action-dropable';
</script>
<div use:draggable={{ container: 'list-a', dragData: item }}>
Drag me
</div>
<div use:droppable={{
container: 'list-b',
callbacks: {
onDrop: (data) => handleDrop(data)
}
}}>
Drop here
</div>
Styling
kuku-ui uses Tailwind CSS 4 with DaisyUI 5. Components accept cls props for custom classes:
<Button cls="w-full mt-4" look="primary">Full Width</Button>
All form components support a col prop ('1'-'12') for grid layout control and a size prop ('xs' | 'sm' | 'md' | 'lg').
Development
npm run dev # Dev server
npm run storybook # Storybook on :6006
npm run check # Type check
npm run build # Build library
npm run format # Prettier
License
MIT