adc-ui-kit
adc-ui-kit
English · ภาษาไทย
Vue 3 UI components styled with Tailwind CSS v4: 33 components and 2 composables. Setup is one CSS import, and you can override the design tokens.
Docs and live examples: https://ui-kit.purepure2553.workers.dev
Requirements
- Vue 3.5+
- Tailwind CSS v4 with
@tailwindcss/vite - vue-router is optional. Without it, everything works except the
toprop ofUiButton.
Installation
npm i adc-ui-kit
npm i -D tailwindcss @tailwindcss/vite # if you don't have them yet
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue(), tailwindcss()],
})
Setup
1. CSS — one line. Use it in place of @import 'tailwindcss'. Don't include both.
/* src/style.css */
@import 'adc-ui-kit/tailwind.css';
This file already includes Tailwind, the design tokens, base styles, and the @source rule that lets Tailwind find the kit's classes.
2. Font. The kit uses Noto Sans Thai but doesn't ship the font file. Load the font yourself, or override --font-sans (see Theming).
<!-- index.html -->
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
3. App.vue. Add the toast and loader outlets once.
<script setup lang="ts">
import { UiLoaderOverlay, UiToast } from 'adc-ui-kit'
</script>
<template>
<RouterView />
<UiToast />
<UiLoaderOverlay />
</template>
Usage
<script setup lang="ts">
import { UiButton, UiInput, UiModal, UiSelect, useToast, type SelectOption } from 'adc-ui-kit'
import { Plus } from 'lucide-vue-next'
import { ref } from 'vue'
const name = ref('')
const branch = ref<string | null>(null)
const open = ref(false)
const toast = useToast()
const BRANCHES: SelectOption[] = [
{ value: 'hq', label: 'Head office' },
{ value: 'east', label: 'East branch' },
]
function save() {
open.value = false
toast.success('Saved')
}
</script>
<template>
<UiButton :icon="Plus" @click="open = true">New customer</UiButton>
<UiModal v-model="open" title="New customer">
<div class="flex flex-col gap-4">
<UiInput v-model="name" label="Name" required />
<UiSelect v-model="branch" label="Branch" :options="BRANCHES" searchable />
</div>
<template #footer>
<UiButton variant="ghost" @click="open = false">Cancel</UiButton>
<UiButton @click="save">Save</UiButton>
</template>
</UiModal>
</template>
Icons come from lucide-vue-next, which is installed along with the kit. Pass the icon component itself, e.g. :icon="Plus".
Theming
Add an @theme block after the import and list only the tokens you want to change. Kit components (including the date picker calendar) and your own classes follow the new values.
@import 'adc-ui-kit/tailwind.css';
@theme {
--color-primary: #7c3aed;
--color-primary-hover: #6d28d9;
--color-primary-soft: #ede9fe;
--radius-control: 12px;
}
| Group | Tokens | Example classes |
|---|---|---|
| Brand | --color-primary · -hover · -soft |
bg-primary text-primary bg-primary-soft |
| Surface & line | --color-ground · --color-panel · --color-line · --color-line-strong |
bg-ground bg-panel border-line |
| Text | --color-ink · --color-muted · --color-faint |
text-ink text-muted text-faint |
| Status | --color-ok · --color-warn · --color-danger (+ -soft) |
text-ok bg-warn-soft text-danger |
| Radius | --radius-control · --radius-card · --radius-auth |
rounded-control rounded-card |
| Font size | --text-tiny · --text-small · --text-body · --text-lead (+ --line-height) |
text-small text-body |
| Font | --font-sans · --font-mono |
font-sans font-mono |
| Z-index | --z-index-dropdown · overlay · modal · toast · tooltip |
z-modal z-toast |
| Layout | --spacing-sidebar |
w-sidebar |
Every token is also a CSS variable, so you can use var(--color-primary) in your own CSS.
Components
v-model is the value you bind to the component. Every form control also accepts label, required, hint, error and disabled.
| Component | Purpose | v-model |
Main props | Events / slots |
|---|---|---|---|---|
UiButton |
Button | – | variant (primary · outline · ghost · danger) · size (sm · md · lg) · icon · iconAlign · block · loading · type · to |
@click · default slot |
UiButtonIcon |
Icon-only button | – | icon · label (required, read by screen readers) · variant · size · loading |
@click |
UiCard |
Content box | – | title · description · icon · padding (none · sm · md · lg) · surface (panel · draft) |
#actions · default |
UiAlert |
Message box | – | tone · title · closable · noIcon |
@close · default |
UiBadge |
Small status label | – | tone · pill |
default |
UiDivider |
Divider line | – | – | default (text in the middle) |
UiSpinner |
Loading spinner | – | size (px) |
– |
UiSkeleton |
Loading placeholder | – | width · height · rounded (sm · full) |
– |
UiEmpty |
Empty state | – | icon · title · description |
default (action) |
UiStat |
KPI number card | – | label · value · trend · trendTone · loading |
– |
UiTable |
Data table | – | columns (TableColumn[]) · rows · rowKey · loading · clickable · sortKey · sortDir · emptyTitle |
@row-click · @sort · #cell-<key> · #empty-action |
UiTabs |
Tabs | T |
items (TabItem[]) |
@change |
UiPagination |
Pagination | number (page) |
total · perPage · siblings |
@change |
UiProgress |
Progress bar | – | value · max · tone · size · label |
– |
UiField |
Label / hint / error wrapper for your own fields | – | label · required · hint · error |
default { id, invalid, describedBy } |
UiInput |
Text input | string |
type · placeholder · icon · clearable · loading · formats · uppercase · maxlength |
@enter · @clear · @change |
UiTextarea |
Multi-line input | string |
rows · maxlength (shows a counter) |
@change |
UiInputCurrency |
Money input (adds commas) | number | null |
decimals · prefix · max |
@change |
UiSelect |
Dropdown | T | null |
options (SelectOption[]) · searchable · clearable · placeholder |
@change |
UiAutocomplete |
Pick from a list or type your own value | string |
options · maxlength · noResultText |
@change |
UiCheckbox |
Checkbox | boolean |
label · description · indeterminate |
@change |
UiRadio |
Radio group | T | null |
options · inline |
@change |
UiToggle |
Switch | boolean |
label · description |
@change |
UiFileUpload |
File upload with drag and drop | File[] |
accept · multiple · maxSizeMb |
@change · @reject |
UiImagePreview |
Image from a URL or a File |
– | source · alt · ratio (square · video) · removable |
@remove |
UiModal |
Dialog | boolean (open) |
title · description · icon · size (sm … xl · full) · dismissible |
@close · default · #footer |
UiConfirm |
Confirmation dialog | boolean (open) |
title · message · confirmText · cancelText · tone · loading |
@confirm · @cancel |
UiToast |
Toast outlet, placed once in App.vue | – | – | driven by useToast() |
UiTooltip |
Tooltip on hover or focus | – | content · placement (top · bottom · left · right) |
default (trigger) |
UiLoaderOverlay |
Full-screen loader outlet, placed once in App.vue | – | – | driven by useLoader() |
UiDatePicker |
Date picker (Thai calendar) | Date | null |
minDate · maxDate · clearable · placeholder |
@change |
UiDateRangePicker |
Date range picker | Date[] |
minDate · maxDate · clearable |
@change |
UiTimePicker |
Time picker | string | null ('HH:mm') |
step (minutes, default 30) · min · max · searchable |
@change |
tone is one of primary, ok, warn, danger or neutral.
Table example
<script setup lang="ts">
import { UiBadge, UiTable, type TableColumn } from 'adc-ui-kit'
const COLUMNS: TableColumn[] = [
{ key: 'code', label: 'Code', mono: true, width: '120px' },
{ key: 'name', label: 'Name' },
{ key: 'status', label: 'Status', align: 'center' },
]
const rows = [{ code: 'C-001', name: 'Somchai', status: 'active' }]
</script>
<template>
<UiTable :columns="COLUMNS" :rows="rows" :row-key="(row) => row.code">
<template #cell-status="{ row }">
<UiBadge tone="ok">{{ row.status }}</UiBadge>
</template>
</UiTable>
</template>
Set sortable: true on a column to get @sort events. The table doesn't reorder rows itself, so sort the data at its source (for example, your API).
Composables
import { useLoader, useToast } from 'adc-ui-kit'
const toast = useToast()
toast.success('Saved')
toast.error('Save failed', { title: 'Error', duration: 0 }) // duration 0 = stays until closed
toast.warning('...')
toast.info('...')
const { withLoader, isLoading } = useLoader()
await withLoader(() => api.save(payload)) // shows the full-screen loader until the task finishes
Both composables need their outlet (<UiToast /> or <UiLoaderOverlay />) in App.vue.
Types
import type { Align, ButtonVariant, SelectOption, Size, SortDirection, TabItem, TableColumn, Tone, Toast } from 'adc-ui-kit'
Notes
- Light mode only.
ภาษาไทย
UI component สำหรับ Vue 3 ที่ใช้ Tailwind CSS v4 มี component 33 ตัวและ composable 2 ตัว ตั้งค่า CSS บรรทัดเดียว และเปลี่ยน design token ได้
เอกสารและตัวอย่างสด: https://ui-kit.purepure2553.workers.dev/?lang=th
สิ่งที่ต้องมี
- Vue 3.5 ขึ้นไป
- Tailwind CSS v4 และ
@tailwindcss/vite - vue-router ไม่บังคับ ไม่มีก็ใช้ได้ทุกตัว ยกเว้น prop
toของUiButtonที่จะไม่ทำงาน
ติดตั้ง
npm i adc-ui-kit
npm i -D tailwindcss @tailwindcss/vite # ถ้ายังไม่มี
// vite.config.ts
import tailwindcss from '@tailwindcss/vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue(), tailwindcss()],
})
ตั้งค่า
1. CSS บรรทัดเดียว ใส่บรรทัดนี้แทน @import 'tailwindcss' ห้ามใส่ทั้งสองบรรทัด
/* src/style.css */
@import 'adc-ui-kit/tailwind.css';
ไฟล์นี้มีครบแล้ว: Tailwind ทั้งชุด, design token, base style และ @source ที่ทำให้ Tailwind หา class ของ kit เจอ
2. ฟอนต์ kit ใช้ Noto Sans Thai แต่ไม่ได้แนบไฟล์ฟอนต์มา ต้องโหลดเอง หรือทับ --font-sans เป็นฟอนต์อื่น (ดู เปลี่ยนธีม)
<!-- index.html -->
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
3. App.vue วางจุดแสดง toast กับ loader ไว้ที่เดียวทั้งแอป
<script setup lang="ts">
import { UiLoaderOverlay, UiToast } from 'adc-ui-kit'
</script>
<template>
<RouterView />
<UiToast />
<UiLoaderOverlay />
</template>
วิธีใช้
<script setup lang="ts">
import { UiButton, UiInput, UiModal, UiSelect, useToast, type SelectOption } from 'adc-ui-kit'
import { Plus } from 'lucide-vue-next'
import { ref } from 'vue'
const name = ref('')
const branch = ref<string | null>(null)
const open = ref(false)
const toast = useToast()
const BRANCHES: SelectOption[] = [
{ value: 'hq', label: 'สำนักงานใหญ่' },
{ value: 'east', label: 'สาขาตะวันออก' },
]
function save() {
open.value = false
toast.success('บันทึกแล้ว')
}
</script>
<template>
<UiButton :icon="Plus" @click="open = true">เพิ่มลูกค้า</UiButton>
<UiModal v-model="open" title="เพิ่มลูกค้า">
<div class="flex flex-col gap-4">
<UiInput v-model="name" label="ชื่อ" required />
<UiSelect v-model="branch" label="สาขา" :options="BRANCHES" searchable />
</div>
<template #footer>
<UiButton variant="ghost" @click="open = false">ยกเลิก</UiButton>
<UiButton @click="save">บันทึก</UiButton>
</template>
</UiModal>
</template>
ไอคอนใช้ lucide-vue-next ซึ่งติดตั้งมาพร้อม kit แล้ว ส่งเป็นตัว component เลย เช่น :icon="Plus"
เปลี่ยนธีม
เขียน @theme ต่อจากบรรทัด import แล้วใส่เฉพาะ token ที่อยากเปลี่ยน ทั้ง component ของ kit (รวมปฏิทินด้วย) และ class ของโปรเจกต์เองจะเปลี่ยนตาม
@import 'adc-ui-kit/tailwind.css';
@theme {
--color-primary: #7c3aed;
--color-primary-hover: #6d28d9;
--color-primary-soft: #ede9fe;
--radius-control: 12px;
}
| กลุ่ม | token | ตัวอย่าง class |
|---|---|---|
| สีแบรนด์ | --color-primary · -hover · -soft |
bg-primary text-primary bg-primary-soft |
| พื้นหลังและเส้น | --color-ground · --color-panel · --color-line · --color-line-strong |
bg-ground bg-panel border-line |
| ตัวอักษร | --color-ink · --color-muted · --color-faint |
text-ink text-muted text-faint |
| สถานะ | --color-ok · --color-warn · --color-danger (+ -soft) |
text-ok bg-warn-soft text-danger |
| มุมโค้ง | --radius-control · --radius-card · --radius-auth |
rounded-control rounded-card |
| ขนาดตัวอักษร | --text-tiny · --text-small · --text-body · --text-lead (+ --line-height) |
text-small text-body |
| ฟอนต์ | --font-sans · --font-mono |
font-sans font-mono |
| ลำดับซ้อนทับ | --z-index-dropdown · overlay · modal · toast · tooltip |
z-modal z-toast |
| โครงหน้า | --spacing-sidebar |
w-sidebar |
token ทุกตัวเป็น CSS variable ด้วย ใน CSS ของโปรเจกต์เขียน var(--color-primary) ได้เลย
Component
v-model คือค่าที่ผูกกับตัวแปร ส่วนช่องฟอร์มทุกตัวรับ label, required, hint, error และ disabled ได้ด้วย
| Component | ใช้ทำอะไร | v-model |
prop หลัก | event / slot |
|---|---|---|---|---|
UiButton |
ปุ่ม | – | variant (primary · outline · ghost · danger) · size (sm · md · lg) · icon · iconAlign · block · loading · type · to |
@click · default slot |
UiButtonIcon |
ปุ่มไอคอนล้วน | – | icon · label (บังคับใส่ ให้โปรแกรมอ่านหน้าจออ่าน) · variant · size · loading |
@click |
UiCard |
กล่องเนื้อหา | – | title · description · icon · padding (none · sm · md · lg) · surface (panel · draft) |
#actions · default |
UiAlert |
กล่องแจ้งเตือน | – | tone · title · closable · noIcon |
@close · default |
UiBadge |
ป้ายสถานะเล็ก | – | tone · pill |
default |
UiDivider |
เส้นคั่น | – | – | default (ข้อความกลางเส้น) |
UiSpinner |
วงกลมหมุนตอนโหลด | – | size (px) |
– |
UiSkeleton |
โครงร่างเทาระหว่างรอข้อมูล | – | width · height · rounded (sm · full) |
– |
UiEmpty |
สถานะไม่มีข้อมูล | – | icon · title · description |
default (ปุ่มชวนทำต่อ) |
UiStat |
การ์ดตัวเลข KPI | – | label · value · trend · trendTone · loading |
– |
UiTable |
ตาราง | – | columns (TableColumn[]) · rows · rowKey · loading · clickable · sortKey · sortDir · emptyTitle |
@row-click · @sort · #cell-<key> · #empty-action |
UiTabs |
แท็บ | T |
items (TabItem[]) |
@change |
UiPagination |
แบ่งหน้า | number (หน้าปัจจุบัน) |
total · perPage · siblings |
@change |
UiProgress |
แถบความคืบหน้า | – | value · max · tone · size · label |
– |
UiField |
ตัวห่อ label / hint / error ให้ช่องที่ทำเอง | – | label · required · hint · error |
default { id, invalid, describedBy } |
UiInput |
ช่องกรอกข้อความ | string |
type · placeholder · icon · clearable · loading · formats · uppercase · maxlength |
@enter · @clear · @change |
UiTextarea |
ช่องกรอกหลายบรรทัด | string |
rows · maxlength (มีตัวนับ) |
@change |
UiInputCurrency |
ช่องจำนวนเงิน (ใส่ comma ให้เอง) | number | null |
decimals · prefix · max |
@change |
UiSelect |
dropdown | T | null |
options (SelectOption[]) · searchable · clearable · placeholder |
@change |
UiAutocomplete |
เลือกจากรายการ หรือพิมพ์เอง | string |
options · maxlength · noResultText |
@change |
UiCheckbox |
ช่องติ๊ก | boolean |
label · description · indeterminate |
@change |
UiRadio |
ตัวเลือกแบบเลือกได้อย่างเดียว | T | null |
options · inline |
@change |
UiToggle |
สวิตช์เปิด/ปิด | boolean |
label · description |
@change |
UiFileUpload |
อัปโหลดไฟล์ (ลากวางได้) | File[] |
accept · multiple · maxSizeMb |
@change · @reject |
UiImagePreview |
แสดงรูปจาก URL หรือ File |
– | source · alt · ratio (square · video) · removable |
@remove |
UiModal |
หน้าต่างซ้อน | boolean (เปิดอยู่ไหม) |
title · description · icon · size (sm … xl · full) · dismissible |
@close · default · #footer |
UiConfirm |
กล่องยืนยัน | boolean (เปิดอยู่ไหม) |
title · message · confirmText · cancelText · tone · loading |
@confirm · @cancel |
UiToast |
จุดแสดง toast (วางที่ App.vue ตัวเดียว) | – | – | สั่งผ่าน useToast() |
UiTooltip |
คำอธิบายลอยตอน hover / focus | – | content · placement (top · bottom · left · right) |
default (ตัวที่ชี้) |
UiLoaderOverlay |
ม่านโหลดเต็มจอ (วางที่ App.vue ตัวเดียว) | – | – | สั่งผ่าน useLoader() |
UiDatePicker |
เลือกวันที่ (ปฏิทินไทย) | Date | null |
minDate · maxDate · clearable · placeholder |
@change |
UiDateRangePicker |
เลือกช่วงวันที่ | Date[] |
minDate · maxDate · clearable |
@change |
UiTimePicker |
เลือกเวลา | string | null ('HH:mm') |
step (นาที ค่าเริ่มต้น 30) · min · max · searchable |
@change |
tone เลือกได้จาก primary, ok, warn, danger และ neutral
ตัวอย่างตาราง
<script setup lang="ts">
import { UiBadge, UiTable, type TableColumn } from 'adc-ui-kit'
const COLUMNS: TableColumn[] = [
{ key: 'code', label: 'รหัส', mono: true, width: '120px' },
{ key: 'name', label: 'ชื่อ' },
{ key: 'status', label: 'สถานะ', align: 'center' },
]
const rows = [{ code: 'C-001', name: 'สมชาย', status: 'ใช้งาน' }]
</script>
<template>
<UiTable :columns="COLUMNS" :rows="rows" :row-key="(row) => row.code">
<template #cell-status="{ row }">
<UiBadge tone="ok">{{ row.status }}</UiBadge>
</template>
</UiTable>
</template>
ใส่ sortable: true ในคอลัมน์แล้วตารางจะยิง @sort ให้ แต่ตารางไม่เรียงแถวเอง ต้องไปเรียงที่ต้นทางของข้อมูล เช่น API
Composable
import { useLoader, useToast } from 'adc-ui-kit'
const toast = useToast()
toast.success('บันทึกแล้ว')
toast.error('บันทึกไม่สำเร็จ', { title: 'ผิดพลาด', duration: 0 }) // duration 0 = ค้างจนกดปิด
toast.warning('...')
toast.info('...')
const { withLoader, isLoading } = useLoader()
await withLoader(() => api.save(payload)) // ม่านโหลดขึ้นจนกว่างานจะเสร็จ
ทั้งสองตัวต้องมีจุดแสดง (<UiToast /> หรือ <UiLoaderOverlay />) อยู่ใน App.vue
Type
import type { Align, ButtonVariant, SelectOption, Size, SortDirection, TabItem, TableColumn, Tone, Toast } from 'adc-ui-kit'
ข้อควรรู้
- มีแต่โทนสว่าง ไม่มี dark mode
Related Packages
Vue 3 UI component kit built with Tailwind CSS v4, with dark and light themes.
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.0 | ^3.5.0 | MIT | |
| 0.1.1 | ^3.5.0 | Proprietary | |
| 0.1.0 | ^3.5.0 | Proprietary |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |