UI Components
Universal DevTools Kit includes a comprehensive UI component library built with Vue 3 and Tailwind CSS v4. All components are designed to work seamlessly in the DevTools dark theme.
Installation
Components are available from the @u-devtools/ui package:
import { UButton, UCard, UInput } from '@u-devtools/ui';Basic Usage
<template>
<div>
<UButton label="Click Me" variant="primary" @click="handleClick" />
<UCard title="My Card">
<p>Card content</p>
</UCard>
</div>
</template>
<script setup lang="ts">
import { UButton, UCard } from '@u-devtools/ui';
const handleClick = () => {
console.log('Button clicked!');
};
</script>Buttons
UButton
A versatile button component with multiple variants and sizes.
Props:
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'- Button stylesize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'- Button size (default:'md')icon?: string- Heroicons icon nameloading?: boolean- Show loading spinnerlabel?: string- Button text (alternative to slot)text?: string- Alias for label
Events:
@click- Click event
Live Demo:
Variants
With Icons
Sizes
Loading State
Interactive Counter
Code Example:
<template>
<div class="flex gap-2 flex-wrap">
<UButton label="Primary" variant="primary" @click="handleClick" />
<UButton label="Save" variant="primary" icon="Check" />
<UButton label="Loading" variant="primary" :loading="isLoading" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UButton } from '@u-devtools/ui';
const isLoading = ref(false);
const handleClick = () => console.log('Clicked!');
</script>Form Inputs
UInput
Text input with support for prefixes, suffixes, and icons.
Props:
modelValue?: string | number- v-model valueplaceholder?: string- Placeholder texttype?: string- Input type (default:'text')size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'- Input size (default:'md')disabled?: boolean- Disable inputreadonly?: boolean- Read-only modeprefix?: string- Text prefixsuffix?: string- Text suffixprefixIcon?: string- Heroicons icon name for prefixsuffixIcon?: string- Heroicons icon name for suffixprepend?: string- External text before inputappend?: string- External text after input
Events:
@update:modelValue- v-model update@focus- Focus event@blur- Blur event@keydown- Keydown event@keyup- Keyup event@enter- Enter key pressed
Live Demo:
Basic Input
Value: (empty)
With Icon
With Prefix/Suffix
Sizes
States
Code Example:
<template>
<UInput
v-model="value"
placeholder="Enter text"
prefix-icon="MagnifyingGlass"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UInput } from '@u-devtools/ui';
const value = ref('');
</script>UTextarea
Multi-line text input.
Props:
modelValue?: string- v-model valueplaceholder?: string- Placeholder textrows?: number- Number of rowssize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'- Textarea sizedisabled?: boolean- Disable textareareadonly?: boolean- Read-only mode
Example:
<template>
<UTextarea
v-model="description"
placeholder="Enter description..."
:rows="4"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UTextarea } from '@u-devtools/ui';
const description = ref('');
</script>USelect
Dropdown select input.
Props:
modelValue?: string | number- v-model valueoptions?: Array<{ label: string; value: string | number }>- Select optionsplaceholder?: string- Placeholder textsize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'- Select sizedisabled?: boolean- Disable select
Example:
<template>
<USelect
v-model="selected"
:options="options"
placeholder="Select an option"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { USelect } from '@u-devtools/ui';
const selected = ref('');
const options = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
];
</script>UDropdown
Advanced dropdown with custom options and icons.
Props:
modelValue?: string- Selected valueoptions?: DropdownOption[]- Dropdown optionsplaceholder?: string- Placeholder textsize?: 'xs' | 'sm' | 'md'- Dropdown sizedisabled?: boolean- Disable dropdowntrigger?: 'button' | 'custom'- Trigger type
Type:
interface DropdownOption {
label: string;
value: string;
icon?: string; // Heroicons icon name
disabled?: boolean;
}Live Demo:
Basic Dropdown
Selected: (none)
Sizes
Code Example:
<template>
<UDropdown
v-model="selected"
:options="options"
placeholder="Select..."
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UDropdown } from '@u-devtools/ui';
import type { DropdownOption } from '@u-devtools/ui';
const selected = ref('');
const options: DropdownOption[] = [
{ label: 'Edit', value: 'edit', icon: 'Pencil' },
{ label: 'Delete', value: 'delete', icon: 'Trash' },
];
</script>UArrayInput
Input for array values (comma-separated or JSON).
Props:
modelValue?: string[]- v-model array valueseparator?: string- Separator character (default:',')placeholder?: string- Placeholder text
Example:
<template>
<UArrayInput
v-model="tags"
placeholder="Enter tags (comma-separated)"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UArrayInput } from '@u-devtools/ui';
const tags = ref(['vue', 'typescript', 'devtools']);
</script>UForm
Form container with validation support.
Props:
modelValue?: Record<string, any>- Form data objectschema?: FormSchema- Form validation schema
Example:
<template>
<UForm v-model="formData" :schema="schema">
<UInput v-model="formData.name" label="Name" />
<UInput v-model="formData.email" label="Email" type="email" />
<UButton label="Submit" @click="handleSubmit" />
</UForm>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UForm, UInput, UButton } from '@u-devtools/ui';
const formData = ref({
name: '',
email: '',
});
const schema = {
name: { required: true },
email: { required: true, type: 'email' },
};
const handleSubmit = () => {
console.log('Form submitted:', formData.value);
};
</script>Data Display
UCard
Card container for grouping content.
Props:
title?: string- Card titlesubtitle?: string- Card subtitlepadding?: boolean- Enable padding (default:true)hover?: boolean- Enable hover effect
Slots:
default- Card contentfooter- Footer content
Live Demo:
Basic Card
This is a card with title and subtitle
Card content goes here. You can put any content inside.
Card with Footer
This card has a footer with action buttons.
Card without Padding
Custom padding applied to inner content.
Code Example:
<template>
<UCard title="Card Title" subtitle="Card subtitle">
<p>Card content goes here.</p>
<template #footer>
<UButton label="Save" variant="primary" />
</template>
</UCard>
</template>
<script setup lang="ts">
import { UCard, UButton } from '@u-devtools/ui';
</script>UBadge
Badge component for labels and status indicators.
Props:
color?: 'gray' | 'red' | 'green' | 'yellow' | 'blue'- Badge colorsize?: 'xs' | 'sm' | 'md'- Badge sizelabel?: string | number- Badge text (alternative to slot)
Live Demo:
Colors
Sizes
With Numbers
Code Example:
<template>
<UBadge label="Success" color="green" />
<UBadge :label="count" color="blue" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UBadge } from '@u-devtools/ui';
const count = ref(42);
</script>UTable
Table component for displaying tabular data.
Props:
columns?: Array<{ key: string; label: string }>- Table columnsdata?: any[]- Table datastriped?: boolean- Striped rows
Example:
<template>
<UTable
:columns="columns"
:data="users"
striped
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UTable } from '@u-devtools/ui';
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'role', label: 'Role' },
];
const users = ref([
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
]);
</script>UKeyValue
Display key-value pairs.
Props:
data?: Record<string, any>- Key-value data objectcollapsible?: boolean- Make nested objects collapsible
Example:
<template>
<UKeyValue :data="metadata" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UKeyValue } from '@u-devtools/ui';
const metadata = ref({
name: 'My Plugin',
version: '1.0.0',
author: 'John Doe',
description: 'A sample plugin',
});
</script>UStat
Statistic display component.
Props:
label?: string- Stat labelvalue?: string | number- Stat valueicon?: string- Heroicons icon nametrend?: 'up' | 'down' | 'neutral'- Trend indicator
Example:
<template>
<div class="grid grid-cols-3 gap-4">
<UStat label="Total Users" :value="1234" icon="Users" trend="up" />
<UStat label="Revenue" value="$12,345" icon="CurrencyDollar" trend="up" />
<UStat label="Errors" :value="5" icon="ExclamationTriangle" trend="down" />
</div>
</template>
<script setup lang="ts">
import { UStat } from '@u-devtools/ui';
</script>Navigation & Layout
UTabs
Tab navigation component.
Props:
items: string[]- Array of tab labelsmodelValue?: string- Active tabmaxVisible?: number- Maximum visible tabs before showing "More" menu
Events:
@update:modelValue- Tab change event
Live Demo:
Basic Tabs
Active tab: Overview
Many Tabs (with "More" menu)
Code Example:
<template>
<UTabs
:items="tabs"
v-model="activeTab"
@update:modelValue="handleTabChange"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UTabs } from '@u-devtools/ui';
const tabs = ['Overview', 'Settings', 'Advanced'];
const activeTab = ref('Overview');
</script>UTabButtons
Button-style tabs.
Props:
items: string[]- Array of tab labelsmodelValue?: string- Active tab
Example:
<template>
<UTabButtons
:items="views"
v-model="activeView"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UTabButtons } from '@u-devtools/ui';
const views = ['List', 'Grid', 'Details'];
const activeView = ref('List');
</script>UAccordion
Collapsible accordion component.
Props:
items: Array<{ title: string; content: string }>- Accordion itemsmultiple?: boolean- Allow multiple items opendefaultOpen?: string[]- Default open items
Example:
<template>
<UAccordion
:items="faqItems"
:multiple="true"
/>
</template>
<script setup lang="ts">
import { UAccordion } from '@u-devtools/ui';
const faqItems = [
{ title: 'What is this?', content: 'This is a DevTools plugin.' },
{ title: 'How to use?', content: 'Follow the documentation.' },
{ title: 'Support?', content: 'Contact us at support@example.com' },
];
</script>USplitter
Resizable split panel component.
Props:
direction?: 'horizontal' | 'vertical'- Split directionsizes?: number[]- Initial panel sizes (percentages)
Example:
<template>
<USplitter :sizes="[30, 70]">
<template #left>
<div class="p-4">Left panel</div>
</template>
<template #right>
<div class="p-4">Right panel</div>
</template>
</USplitter>
</template>
<script setup lang="ts">
import { USplitter } from '@u-devtools/ui';
</script>Trees & Lists
UTreeView
Tree view component for hierarchical data.
Props:
nodes: TreeNode[]- Tree nodestitle?: string- Tree titleshowHeader?: boolean- Show header (default:true)expandAll?: boolean- Expand all nodes initiallyselectable?: boolean- Allow node selection (default:true)indentSize?: number- Indentation size in pixels (default:20)
Type:
interface TreeNode {
id: string | number;
label: string;
children?: TreeNode[];
data?: Record<string, unknown>;
isExpanded?: boolean;
isSelected?: boolean;
isCurrent?: boolean;
icon?: string;
[key: string]: unknown;
}Events:
@nodeClick- Node clicked@nodeSelect- Node selected@nodeExpand- Node expanded/collapsed
Live Demo:
File Tree
Clicked: (none)
Selected: (none)
Code Example:
<template>
<UTreeView
:nodes="treeData"
title="File Explorer"
@nodeClick="handleNodeClick"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UTreeView } from '@u-devtools/ui';
import type { TreeNode } from '@u-devtools/ui';
const treeData = ref<TreeNode[]>([
{
id: '1',
label: 'src',
icon: 'Folder',
children: [
{ id: '1-1', label: 'components', icon: 'Folder' },
],
},
]);
</script>UFileTree
Specialized file tree component.
Props:
nodes: TreeNode[]- File tree nodesshowHeader?: boolean- Show header
Example:
<template>
<UFileTree :nodes="fileTree" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UFileTree } from '@u-devtools/ui';
import type { TreeNode } from '@u-devtools/ui';
const fileTree = ref<TreeNode[]>([
{
id: 'src',
label: 'src',
children: [
{ id: 'src/components', label: 'components' },
{ id: 'src/utils.ts', label: 'utils.ts' },
],
},
]);
</script>UVirtualList
Virtualized list for large datasets.
Props:
items: any[]- List itemsitemHeight?: number- Height of each item in pixelsheight?: number- Container height
Slots:
default- Item template (receivesitemandindex)
Example:
<template>
<UVirtualList
:items="largeDataset"
:item-height="50"
:height="400"
>
<template #default="{ item, index }">
<div class="p-2 border-b border-zinc-800">
{{ index + 1 }}. {{ item.name }}
</div>
</template>
</UVirtualList>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UVirtualList } from '@u-devtools/ui';
const largeDataset = ref(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i + 1}`,
}))
);
</script>Code & Data
UCodeBlock
Syntax-highlighted code block.
Props:
code?: string- Code contentlanguage?: string- Programming languageshowLineNumbers?: boolean- Show line numberscopyable?: boolean- Show copy button
Example:
<template>
<UCodeBlock
:code="codeExample"
language="typescript"
:show-line-numbers="true"
:copyable="true"
/>
</template>
<script setup lang="ts">
import { UCodeBlock } from '@u-devtools/ui';
const codeExample = `function greet(name: string) {
return \`Hello, \${name}!\`;
}`;
</script>UJsonTree
Interactive JSON tree viewer.
Props:
data?: any- JSON data to displayexpandDepth?: number- Initial expansion depthselectable?: boolean- Allow value selection
Example:
<template>
<UJsonTree
:data="jsonData"
:expand-depth="2"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UJsonTree } from '@u-devtools/ui';
const jsonData = ref({
name: 'My Plugin',
version: '1.0.0',
config: {
enabled: true,
options: ['option1', 'option2'],
},
});
</script>UDomNode
DOM node inspector component.
Props:
node?: HTMLElement- DOM node to inspectshowAttributes?: boolean- Show HTML attributesshowStyles?: boolean- Show computed styles
Example:
<template>
<UDomNode
:node="selectedNode"
:show-attributes="true"
:show-styles="true"
/>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { UDomNode } from '@u-devtools/ui';
const selectedNode = ref<HTMLElement | null>(null);
onMounted(() => {
selectedNode.value = document.querySelector('.my-element') as HTMLElement;
});
</script>Colors
UColorPicker
Color picker with Tailwind color support.
Props:
modelValue?: string- Selected color (Tailwind class)type?: 'text' | 'bg' | 'border'- Color typesize?: 'xs' | 'sm' | 'md'- Picker sizecolors?: ColorOption[]- Available colorsshowOpacity?: boolean- Show opacity selectorshowNone?: boolean- Show "None" option
Type:
interface ColorOption {
name: string;
value: string; // Tailwind class
hex?: string; // Hex color for preview
}Example:
<template>
<UColorPicker
v-model="selectedColor"
type="bg"
:colors="colorOptions"
:show-opacity="true"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UColorPicker } from '@u-devtools/ui';
import type { ColorOption } from '@u-devtools/ui';
const selectedColor = ref('bg-blue-500');
const colorOptions: ColorOption[] = [
{ name: 'Red', value: 'bg-red-500', hex: '#ef4444' },
{ name: 'Blue', value: 'bg-blue-500', hex: '#3b82f6' },
{ name: 'Green', value: 'bg-green-500', hex: '#22c55e' },
];
</script>UColorGrid
Grid display for color options.
Props:
colors: ColorOption[]- Color optionscurrentColor?: string | null- Currently selected coloronSelect: (value: string) => void- Selection handler
Example:
<template>
<UColorGrid
:colors="colors"
:current-color="selectedColor"
:on-select="handleColorSelect"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UColorGrid } from '@u-devtools/ui';
import type { ColorOption } from '@u-devtools/ui';
const selectedColor = ref<string | null>(null);
const colors: ColorOption[] = [
{ name: 'Red', value: 'red-500', hex: '#ef4444' },
{ name: 'Blue', value: 'blue-500', hex: '#3b82f6' },
];
const handleColorSelect = (value: string) => {
selectedColor.value = value;
};
</script>Feedback
UModal
Modal dialog component.
Props:
visible: boolean- Modal visibilitytitle?: string- Modal titlesize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full'- Modal sizeclosable?: boolean- Show close button (default:true)
Events:
@close- Close event
Live Demo:
Basic Modal
<UModal
v-model:visible="showModal"
title="Confirm Action"
size="md"
>
<p class="text-gray-300 mb-4">Are you sure you want to proceed with this action?</p>
<template #footer>
<div class="flex gap-2 justify-end">
<UButton label="Cancel" variant="secondary" @click="showModal = false" />
<UButton label="Confirm" variant="primary" @click="showModal = false" />
</div>
</template>
</UModal>
Modal Sizes
<UModal
v-model:visible="showSmallModal"
title="Small Modal"
size="sm"
>
<p class="text-gray-300">This is a small modal dialog.</p>
</UModal>
<UModal
v-model:visible="showLargeModal"
title="Large Modal"
size="lg"
>
<p class="text-gray-300">This is a large modal dialog with more space for content.</p>
</UModal>
Code Example:
<template>
<UButton label="Open Modal" @click="showModal = true" />
<UModal
v-model:visible="showModal"
title="Confirm Action"
size="md"
>
<p>Are you sure?</p>
<template #footer>
<UButton label="Cancel" @click="showModal = false" />
</template>
</UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { UButton, UModal } from '@u-devtools/ui';
const showModal = ref(false);
</script>ULoading
Loading spinner component.
Props:
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'- Spinner sizetext?: string- Loading text
Live Demo:
Loading States
Code Example:
<template>
<ULoading text="Loading data..." />
</template>
<script setup lang="ts">
import { ULoading } from '@u-devtools/ui';
</script>UEmpty
Empty state component.
Props:
title?: string- Empty state titledescription?: string- Empty state descriptionicon?: string- Heroicons icon name
Live Demo:
No items found
Try adding some items to get started
No data available
There is nothing to display here
Code Example:
<template>
<UEmpty
title="No items found"
description="Try adding some items"
icon="Inbox"
/>
</template>
<script setup lang="ts">
import { UEmpty } from '@u-devtools/ui';
</script>Utilities
UIcon
Icon component using Heroicons.
Props:
name: string- Heroicons icon namesize?: string- Icon size (CSS class, e.g.,'w-4 h-4')
Example:
<template>
<div class="flex gap-2">
<UIcon name="Home" class="w-5 h-5" />
<UIcon name="User" class="w-6 h-6" />
<UIcon name="Settings" class="w-4 h-4" />
</div>
</template>
<script setup lang="ts">
import { UIcon } from '@u-devtools/ui';
</script>UPluginLayout
Layout component specifically designed for plugin panels.
Props:
title?: string- Panel titleshowHeader?: boolean- Show header
Slots:
default- Main contentsidebar- Sidebar contentheader- Custom header
Example:
<template>
<UPluginLayout title="My Plugin">
<template #sidebar>
<div class="p-4">Sidebar content</div>
</template>
<template #default>
<div class="p-4">Main content</div>
</template>
</UPluginLayout>
</template>
<script setup lang="ts">
import { UPluginLayout } from '@u-devtools/ui';
</script>Complete Example
Here's a complete example combining multiple components:
<template>
<UPluginLayout title="User Management">
<template #sidebar>
<UTreeView
:nodes="menuItems"
@nodeSelect="handleMenuSelect"
/>
</template>
<template #default>
<UCard title="Users" subtitle="Manage your users">
<div class="space-y-4">
<!-- Search -->
<UInput
v-model="searchQuery"
placeholder="Search users..."
prefix-icon="MagnifyingGlass"
/>
<!-- Actions -->
<div class="flex gap-2">
<UButton
label="Add User"
variant="primary"
icon="Plus"
@click="showAddModal = true"
/>
<UButton
label="Export"
variant="secondary"
icon="ArrowDownTray"
/>
</div>
<!-- Table -->
<UTable
:columns="columns"
:data="filteredUsers"
striped
/>
</div>
</UCard>
<!-- Add User Modal -->
<UModal
v-model:visible="showAddModal"
title="Add New User"
size="md"
>
<UForm v-model="newUser" :schema="userSchema">
<UInput v-model="newUser.name" label="Name" />
<UInput v-model="newUser.email" label="Email" type="email" />
<USelect
v-model="newUser.role"
:options="roleOptions"
label="Role"
/>
</UForm>
<template #footer>
<div class="flex gap-2 justify-end">
<UButton
label="Cancel"
variant="secondary"
@click="showAddModal = false"
/>
<UButton
label="Save"
variant="primary"
@click="handleSave"
/>
</div>
</template>
</UModal>
</template>
</UPluginLayout>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import {
UPluginLayout,
UTreeView,
UCard,
UInput,
UButton,
UTable,
UModal,
UForm,
USelect,
} from '@u-devtools/ui';
import type { TreeNode } from '@u-devtools/ui';
const searchQuery = ref('');
const showAddModal = ref(false);
const newUser = ref({ name: '', email: '', role: '' });
const menuItems: TreeNode[] = [
{ id: 'users', label: 'Users', icon: 'Users' },
{ id: 'roles', label: 'Roles', icon: 'ShieldCheck' },
];
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'role', label: 'Role' },
];
const users = ref([
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
]);
const filteredUsers = computed(() => {
if (!searchQuery.value) return users.value;
return users.value.filter(user =>
user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
const roleOptions = [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
];
const userSchema = {
name: { required: true },
email: { required: true, type: 'email' },
role: { required: true },
};
const handleMenuSelect = (node: TreeNode) => {
console.log('Menu selected:', node);
};
const handleSave = () => {
users.value.push({ ...newUser.value });
newUser.value = { name: '', email: '', role: '' };
showAddModal.value = false;
};
</script>See Also
- API Reference - Complete component API documentation
- Plugin Development Guide - Learn how to build plugins
- Architecture Guide - Understand the system architecture