A comprehensive Vue 3 + Nuxt 3 UI component library with advanced form generation capabilities, built with TypeScript, TailwindCSS, and modern web technologies.
- π¨ Modern UI Components - Built with shadcn/ui and TailwindCSS
- π§ Dynamic Form Generation - Automatic form creation from Zod schemas
- π Chart Components - Data visualization with vue-charts
- π Dark Mode Support - Built-in theme switching
- π§© Modular Architecture - Import only what you need
- π Type Safety - Full TypeScript support
- π― Form Validation - Advanced validation with vee-validate and Zod
- π Resource Management - Smart resource field handling and linking
- π± Responsive Design - Mobile-first approach
- βΏ Accessibility - ARIA compliant components
Add the layer to your Nuxt project:
npm install @damourlabs/ui
# or
pnpm add @damourlabs/ui
# or
yarn add @damourlabs/uiAdd the layer to your nuxt.config.ts:
export default defineNuxtConfig({
extends: ['@damourlabs/ui'],
// your config
})That's it! The layer will automatically configure:
- TailwindCSS with custom theme
- Component auto-imports with
Uiprefix - Dark mode support
- Form validation setup
- Chart components
The crown jewel of this library is the dynamic form generation system that automatically creates forms from Zod schemas.
<template>
<div>
<UiDynamicForm
:schema="formSchema"
:submit-fn="handleSubmit"
:sections="true"
/>
</div>
</template>
<script setup lang="ts">
import { z } from 'zod'
import { createDynamicForm } from '@damourlabs/ui/utils/form'
// Define your schema
const userSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be at least 18 years old'),
isActive: z.boolean().default(true),
role: z.enum(['admin', 'user', 'moderator']),
profile: z.object({
bio: z.string().optional(),
website: z.string().url().optional(),
})
})
// Generate the form
const formSchema = createDynamicForm(userSchema)
const handleSubmit = (values: any) => {
console.log('Form submitted:', values)
}
</script>import { createDynamicForm, type CreateDynamicFormOptions } from '@damourlabs/ui/utils/form'
const options: CreateDynamicFormOptions = {
// Configure resource fields for foreign key relationships
resourceFields: [
{
field: 'user',
store: 'users',
displayField: 'email',
searchFields: ['name', 'email']
},
{
field: 'project',
store: 'projects',
displayField: 'name'
}
],
// Fields to exclude from form generation
fieldsToIgnore: ['id', 'createdAt', 'updatedAt'],
// Custom field type mappings
fieldTypeMapping: {
[z.ZodFirstPartyTypeKind.ZodString]: {
as: 'textarea',
inputType: 'text',
handler: (fieldSchema, field, context) => {
if (context.key === 'description') {
field.as = 'textarea'
}
}
}
}
}
const formSchema = createDynamicForm(userSchema, options)All components are built with accessibility and customization in mind:
<template>
<UiButton variant="default">Default Button</UiButton>
<UiButton variant="destructive">Delete</UiButton>
<UiButton variant="outline">Outlined</UiButton>
<UiButton variant="ghost">Ghost</UiButton>
<UiButton variant="success">Success</UiButton>
</template><template>
<UiCard>
<UiCardHeader>
<UiCardTitle>Card Title</UiCardTitle>
<UiCardDescription>Card description</UiCardDescription>
</UiCardHeader>
<UiCardContent>
<!-- Your content -->
</UiCardContent>
<UiCardFooter>
<!-- Footer actions -->
</UiCardFooter>
</UiCard>
</template><template>
<UiDataTable
:data="tableData"
:columns="columns"
:pagination="true"
:sorting="true"
:filtering="true"
/>
</template>Built-in chart components for data visualization:
<template>
<UiBarChart
:data="chartData"
:categories="['sales', 'revenue']"
:index="'month'"
:colors="['#3b82f6', '#ef4444']"
/>
<UiLineChart
:data="timeSeriesData"
:categories="['users', 'sessions']"
:index="'date'"
/>
</template>The dynamic form system supports all Zod schema types with intelligent field mapping:
const schema = z.object({
name: z.string(), // β text input
email: z.string().email(), // β email input
website: z.string().url(), // β url input
birthday: z.string().date(), // β date picker
userId: z.string().uuid(), // β resource finder (if configured)
description: z.string(), // β textarea (with custom mapping)
})const schema = z.object({
age: z.number(), // β number input with steppers
price: z.number().min(0), // β number input with validation
rating: z.number().min(1).max(5), // β range slider (with custom mapping)
})const schema = z.object({
isActive: z.boolean(), // β checkbox
agreeToTerms: z.boolean(), // β checkbox with label
})const schema = z.object({
status: z.enum(['active', 'inactive', 'pending']), // β select dropdown
priority: z.enum(['low', 'medium', 'high']), // β select dropdown
})const schema = z.object({
createdAt: z.date(), // β date input
scheduledFor: z.date(), // β calendar date picker
})const schema = z.object({
tags: z.array(z.string()), // β dynamic array of text inputs
members: z.array(z.object({ // β dynamic array of nested forms
name: z.string(),
role: z.enum(['admin', 'user'])
})),
userIds: z.array(z.string().uuid()), // β resource finder (multi-select)
})const schema = z.object({
address: z.object({ // β collapsible nested form section
street: z.string(),
city: z.string(),
country: z.string(),
}),
preferences: z.record(z.string()), // β dynamic key-value pairs
})The library includes a powerful resource field system for handling relationships and foreign keys:
const options: CreateDynamicFormOptions = {
resourceFields: [
{
field: 'user', // Field name (without 'Id' suffix)
store: 'usersStore', // Pinia store for data fetching
displayField: 'email', // Field to display in the UI
searchFields: ['name', 'email'] // Fields to search in
}
]
}
// This will automatically convert:
// userId: z.string().uuid() β Resource finder component<template>
<UiResourceFinder
v-model="selectedUserId"
:resource-store="usersStore"
:display-field="'email'"
:search-fields="['name', 'email']"
:allow-clear="true"
placeholder="Search for a user..."
/>
</template>import { createResourceFinderField } from '@damourlabs/ui/utils/form'
const userField = createResourceFinderField(
'userId',
'User',
'users',
{
description: 'Select the user for this record',
displayField: 'email',
subTextField: 'name',
searchFields: ['name', 'email', 'username'],
rules: z.string().uuid().optional()
}
)The library uses TailwindCSS v4 with a custom configuration. You can extend the theme in your project:
// tailwind.config.js
import { damourTheme } from '@damourlabs/ui/tailwind.config'
export default {
extends: [damourTheme],
theme: {
extend: {
colors: {
// Your custom colors
}
}
}
}Dark mode is automatically configured and can be toggled:
<template>
<UiDarkModeToggleSwitch />
</template>All components accept custom classes and can be styled:
<template>
<UiButton
class="bg-gradient-to-r from-purple-500 to-pink-500"
variant="outline"
>
Custom Styled Button
</UiButton>
</template><template>
<UiNavMain>
<UiNavTopLevel>
<UiNavLinks :links="navigationLinks" />
<UiNavUser :user="currentUser" />
</UiNavTopLevel>
<UiNavSideBar>
<UiNavQuickSettings />
</UiNavSideBar>
</UiNavMain>
</template><template>
<UiHero :actions="heroActions">
<template #title>
Welcome to Our Platform
</template>
<template #description>
Build amazing applications with our comprehensive UI library
</template>
</UiHero>
</template>const schema = z.object({
email: z.string()
.email('Please enter a valid email')
.min(1, 'Email is required'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, 'Password must contain uppercase, lowercase and number'),
age: z.number()
.min(18, 'Must be at least 18 years old')
.max(120, 'Please enter a valid age')
})import { createFieldHandler } from '@damourlabs/ui/utils/form'
const customEmailHandler = createFieldHandler<z.ZodString>((fieldSchema, field, context) => {
if (context.key.includes('email')) {
field.inputType = 'email'
field.rules = z.string().email().refine(async (email) => {
// Custom async validation
const exists = await checkEmailExists(email)
return !exists
}, 'Email already exists')
}
})<template>
<div class="container mx-auto p-6">
<UiDynamicForm
:schema="userFormSchema"
:submit-fn="handleUserSubmit"
:sections="true"
/>
</div>
</template>
<script setup lang="ts">
import { z } from 'zod'
import { createDynamicForm } from '@damourlabs/ui/utils/form'
const userSchema = z.object({
// Basic Information
firstName: z.string().min(2, 'First name must be at least 2 characters'),
lastName: z.string().min(2, 'Last name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
phone: z.string().optional(),
// Account Settings
role: z.enum(['admin', 'user', 'moderator']).default('user'),
isActive: z.boolean().default(true),
departmentId: z.string().uuid().optional(),
managerId: z.string().uuid().optional(),
// Profile Information
profile: z.object({
bio: z.string().max(500, 'Bio must be less than 500 characters').optional(),
website: z.string().url('Invalid website URL').optional(),
avatar: z.string().url().optional(),
socialLinks: z.array(z.object({
platform: z.enum(['twitter', 'linkedin', 'github']),
url: z.string().url()
})).optional()
}),
// Preferences
preferences: z.object({
emailNotifications: z.boolean().default(true),
theme: z.enum(['light', 'dark', 'system']).default('system'),
language: z.enum(['en', 'es', 'fr']).default('en')
}),
// Permissions
permissions: z.array(z.enum(['read', 'write', 'delete', 'admin'])).default(['read']),
// Projects
projects: z.array(z.object({
projectId: z.string().uuid(),
role: z.enum(['owner', 'collaborator', 'viewer']),
joinedAt: z.date().default(() => new Date())
})).optional()
})
const userFormSchema = createDynamicForm(userSchema, {
resourceFields: [
{
field: 'department',
store: 'departments',
displayField: 'name',
searchFields: ['name', 'code']
},
{
field: 'manager',
store: 'users',
displayField: 'email',
searchFields: ['firstName', 'lastName', 'email']
},
{
field: 'project',
store: 'projects',
displayField: 'name',
searchFields: ['name', 'description']
}
],
fieldsToIgnore: ['id', 'createdAt', 'updatedAt']
})
const handleUserSubmit = async (values: any) => {
try {
await $fetch('/api/users', {
method: 'POST',
body: values
})
// Handle success
} catch (error) {
// Handle error
}
}
</script>const productSchema = z.object({
name: z.string().min(3, 'Product name must be at least 3 characters'),
description: z.string().min(10, 'Description must be at least 10 characters'),
price: z.number().min(0.01, 'Price must be greater than 0'),
categoryId: z.string().uuid(),
// Product variants
variants: z.array(z.object({
name: z.string(),
sku: z.string(),
price: z.number().min(0),
inventory: z.number().min(0).int(),
attributes: z.record(z.string())
})),
// SEO
seo: z.object({
title: z.string().max(60, 'SEO title must be less than 60 characters').optional(),
description: z.string().max(160, 'SEO description must be less than 160 characters').optional(),
keywords: z.array(z.string()).optional()
}),
// Media
images: z.array(z.string().url()),
// Shipping
shipping: z.object({
weight: z.number().min(0),
dimensions: z.object({
length: z.number().min(0),
width: z.number().min(0),
height: z.number().min(0)
}),
freeShipping: z.boolean().default(false)
})
})
const productFormSchema = createDynamicForm(productSchema, {
resourceFields: [
{ field: 'category', store: 'categories', displayField: 'name' }
]
})# Clone the repository
git clone https://github.com/damourlabs/ui.git
cd ui
# Install dependencies
pnpm install
# Start development server
pnpm dev# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage# Build for production
pnpm build
# Generate static files
pnpm generateGenerates a dynamic form from a Zod schema.
Parameters:
schema: z.ZodObject- The Zod schema to generate the form fromoptions?: CreateDynamicFormOptions- Configuration options
Returns: FormSchema<RuleExpression<unknown>>
Creates a resource finder field configuration.
Parameters:
name: string- Field namelabel: string- Field labelstoreKey: string- Pinia store key for data fetchingoptions?: object- Additional configuration
Creates a custom field handler for specific field types.
Parameters:
handler: FieldHandler<T>- The handler function
Creates custom field type mappings.
Parameters:
mapping: Partial<FieldTypeMapping>- The mapping configuration
See individual component documentation for detailed prop specifications.
We welcome contributions! Please see our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- shadcn/ui for the component foundation
- TailwindCSS for styling
- Zod for schema validation
- vee-validate for form validation
- Vue 3 and Nuxt 3 for the framework
Made with β€οΈ by DamourLabs
Build the application for production:
pnpm buildOr statically generate it with:
pnpm generateLocally preview production build:
pnpm previewCheckout the deployment documentation for more information.