Common Patterns
Real-world usage patterns and composition examples for building applications with MeldUI.
Form with Validation
A complete form using Field, Input, Select, and Button with vee-validate + zod:
<script setup>
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormMessage,
Input,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
Button,
toast,
} from '@meldui/vue'
const schema = toTypedSchema(
z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
role: z.enum(['admin', 'user', 'guest']),
}),
)
const { handleSubmit } = useForm({ validationSchema: schema })
const onSubmit = handleSubmit((values) => {
toast.success(`Created ${values.name}`)
})
</script>
<template>
<form @submit="onSubmit" class="space-y-4 max-w-md">
<FormField v-slot="{ componentField }" name="name">
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input v-bind="componentField" placeholder="John Doe" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input v-bind="componentField" type="email" placeholder="[email protected]" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="role">
<FormItem>
<FormLabel>Role</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger>
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="user">User</SelectItem>
<SelectItem value="guest">Guest</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit">Create User</Button>
</form>
</template>
Dashboard Layout with Sidebar
A full application layout using SidebarProvider, Sidebar, and SidebarInset:
<script setup>
import {
SidebarProvider,
Sidebar,
SidebarContent,
SidebarHeader,
SidebarFooter,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarGroup,
SidebarGroupLabel,
SidebarGroupContent,
SidebarTrigger,
SidebarInset,
} from '@meldui/vue'
import { IconHome, IconUsers, IconSettings, IconChartBar } from '@meldui/tabler-vue'
</script>
<template>
<SidebarProvider>
<Sidebar>
<SidebarHeader>
<span class="px-2 text-lg font-bold">My App</span>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Menu</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton is-active>
<IconHome class="size-4" /> Dashboard
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton> <IconUsers class="size-4" /> Users </SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton> <IconChartBar class="size-4" /> Analytics </SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton> <IconSettings class="size-4" /> Settings </SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>
<SidebarInset>
<header class="flex items-center gap-2 border-b p-4">
<SidebarTrigger />
<h1 class="text-lg font-semibold">Dashboard</h1>
</header>
<main class="p-6">
<!-- Page content -->
</main>
</SidebarInset>
</SidebarProvider>
</template>
Data Table with Server-Side Fetch
A complete DataTable setup with API fetching, filters, and selection:
<script setup>
import { ref, watch } from 'vue'
import {
DataTable,
cellRenderers,
createColumnHelper,
useDataTableController,
} from '@meldui/vue'
import type { DataTableFilterField, BulkActionOption } from '@meldui/vue'
import { IconTrash } from '@meldui/tabler-vue'
interface User {
id: string
name: string
email: string
role: 'admin' | 'user' | 'guest'
status: 'active' | 'inactive'
}
const helper = createColumnHelper<User>()
const columns = [
helper.selection(),
helper.accessor('name', { title: 'Name', enableSorting: true }),
helper.accessor('email', { title: 'Email' }),
helper.accessor('role', {
title: 'Role',
cell: cellRenderers.badge({
variants: {
admin: { label: 'Admin', variant: 'default' },
user: { label: 'User', variant: 'secondary' },
guest: { label: 'Guest', variant: 'neutral' },
},
}),
}),
helper.accessor('status', {
title: 'Status',
cell: cellRenderers.badge({
variants: {
active: { label: 'Active', variant: 'success' },
inactive: { label: 'Inactive', variant: 'destructive' },
},
}),
}),
helper.actions({
display: 'dropdown',
actions: [
{ label: 'Edit', onClick: (row) => router.push(`/users/${row.id}`) },
{ label: 'Delete', variant: 'destructive', onClick: (row) => deleteUser(row.id) },
],
}),
]
const filterFields: DataTableFilterField<User>[] = [
{ id: 'name', label: 'Name', type: 'text' },
{
id: 'role', label: 'Role', type: 'select',
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
{ label: 'Guest', value: 'guest' },
],
},
{
id: 'status', label: 'Status', type: 'select',
options: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
},
]
const bulkActions: BulkActionOption<User>[] = [
{
label: 'Delete',
icon: IconTrash,
variant: 'destructive',
action: (ids) => deleteUsers(ids),
},
]
const { sorting, filters, pagination, state } = useDataTableController({ pageSize: 20 })
const data = ref<User[]>([])
const pageCount = ref(1)
async function fetchPage() {
const response = await api.get('/users', {
params: {
page: state.value.pagination.pageIndex + 1,
per_page: state.value.pagination.pageSize,
sorting: state.value.sorting,
filters: state.value.filters,
},
})
data.value = response.data
pageCount.value = response.meta.total_pages
}
watch(state, fetchPage, { deep: true })
fetchPage()
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:page-count="pageCount"
:filter-fields="filterFields"
:filter-search="{ id: 'name', placeholder: 'Search users...' }"
:bulk-select-options="bulkActions"
enable-sorting
enable-filter
enable-pagination
enable-row-selection
v-model:sorting="sorting"
v-model:filters="filters"
v-model:pagination="pagination"
/>
</template>
Chart Dashboard
Multiple charts in a grid layout:
<script setup>
import { MeldLineChart, MeldBarChart, MeldDonutChart } from '@meldui/charts-vue'
import { Card, CardHeader, CardTitle, CardContent } from '@meldui/vue'
</script>
<template>
<div class="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Revenue Trend</CardTitle>
</CardHeader>
<CardContent>
<MeldLineChart :config="revenueConfig" :height="250" />
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Sales by Category</CardTitle>
</CardHeader>
<CardContent>
<MeldBarChart :config="salesConfig" :height="250" />
</CardContent>
</Card>
<Card class="md:col-span-2">
<CardHeader>
<CardTitle>Traffic Sources</CardTitle>
</CardHeader>
<CardContent>
<MeldDonutChart :config="trafficConfig" :height="300" />
</CardContent>
</Card>
</div>
</template>
Task Manager Example App
A complete working application built with MeldUI is available in the repository:
Features demonstrated:
- Dashboard with stats cards and charts
- Task CRUD with DataTable, filtering, and sorting
- Project organization with sidebar navigation
- Settings with theme switching (light/dark)
- Form validation with vee-validate + zod
- Toast notifications with Sonner
Run it locally:
pnpm dev:example