--- name: "Directus UI Extensions Mastery" description: "Build Vue 3 UI extensions for Directus with modern patterns, real-time data, and responsive design" version: 1.0.0 author: "Directus Development System" tags: ["directus", "vue3", "ui", "extensions", "panels", "interfaces", "displays", "layouts"] --- # Directus UI Extensions Mastery ## Overview This skill provides expert guidance for building production-ready Vue 3 UI extensions in Directus. Master the creation of custom panels, interfaces, displays, and layouts using the @directus/extensions-sdk with modern Vue 3 Composition API patterns. Implement real-time data visualization, responsive design, and seamless integration with Directus' component library. ## When to Use This Skill - Building custom dashboard panels for data visualization - Creating specialized input interfaces for complex data types - Developing custom collection displays and layouts - Implementing real-time components with WebSocket integration - Adding glass morphism or modern UI design patterns - Ensuring mobile parity and responsive design - Integrating with Directus theme system - Creating reusable UI components for teams ## Core Concepts ### Extension Types 1. **Panels** - Dashboard widgets for Insights module 2. **Interfaces** - Custom input components for data entry 3. **Displays** - Custom rendering of field values 4. **Layouts** - Alternative collection views 5. **Modules** - Complete custom sections in Directus ### Technology Stack - **Vue 3** with Composition API - **TypeScript** for type safety - **@directus/extensions-sdk** for Directus integration - **Vite** for building and development - **Pinia** for state management (via useStores) - **Vue Router** for navigation (in modules) ## Process: Building a Custom Panel ### Step 1: Initialize Extension ```bash # Create new panel extension npx create-directus-extension@latest # Select options: # > panel # > my-custom-panel # > typescript ``` ### Step 2: Configure Panel Metadata ```typescript // src/index.ts import { definePanel } from '@directus/extensions-sdk'; import PanelComponent from './panel.vue'; export default definePanel({ id: 'custom-analytics', name: 'Analytics Dashboard', icon: 'analytics', description: 'Real-time analytics and metrics', component: PanelComponent, minWidth: 12, minHeight: 8, options: [ { field: 'collection', type: 'string', name: 'Collection', meta: { interface: 'system-collection', width: 'half', }, }, { field: 'dateField', type: 'string', name: 'Date Field', meta: { interface: 'system-field', options: { collectionField: 'collection', typeAllowList: ['datetime', 'date', 'timestamp'], }, width: 'half', }, }, { field: 'refreshInterval', type: 'integer', name: 'Refresh Interval (seconds)', meta: { interface: 'input', width: 'half', }, schema: { default_value: 30, }, }, ], }); ``` ### Step 3: Implement Vue Component ```vue ``` ## Process: Building a Custom Interface ### Step 1: Interface Structure ```typescript // src/index.ts import { defineInterface } from '@directus/extensions-sdk'; import InterfaceComponent from './interface.vue'; export default defineInterface({ id: 'color-palette', name: 'Color Palette', icon: 'palette', description: 'Select colors from a predefined palette', component: InterfaceComponent, types: ['string', 'json'], group: 'selection', options: [ { field: 'palette', type: 'json', name: 'Color Palette', meta: { interface: 'code', options: { language: 'json', }, }, schema: { default_value: [ '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57', '#FF9FF3', ], }, }, { field: 'allowMultiple', type: 'boolean', name: 'Allow Multiple Selection', meta: { interface: 'boolean', width: 'half', }, schema: { default_value: false, }, }, ], }); ``` ### Step 2: Interface Component ```vue ``` ## Using Directus Composables ### Available Composables ```typescript import { useApi, // API client for making requests useStores, // Access to Directus stores useSync, // Sync data between components useCollection, // Collection metadata useItems, // Items management useLayout, // Layout configuration usePermissions,// User permissions useFilterFields,// Field filtering } from '@directus/extensions-sdk'; ``` ### API Usage Examples ```typescript // Fetch data from API const api = useApi(); // GET request const response = await api.get('/items/articles', { params: { filter: { status: { _eq: 'published' } }, limit: 10, sort: ['-date_created'], }, }); // POST request await api.post('/items/comments', { article: 1, content: 'Great article!', author: 'current-user-id', }); // File upload const formData = new FormData(); formData.append('file', fileBlob); await api.post('/files', formData); ``` ### Store Usage Examples ```typescript const { useItemsStore, useCollectionsStore, useFieldsStore } = useStores(); // Items store const itemsStore = useItemsStore(); const items = await itemsStore.getItems('articles', { limit: 10, fields: ['id', 'title', 'content'], }); // Collections store const collectionsStore = useCollectionsStore(); const collections = collectionsStore.collections; // Fields store const fieldsStore = useFieldsStore(); const fields = fieldsStore.getFieldsForCollection('articles'); ``` ## Real-time Features with WebSockets ### Setup WebSocket Connection ```typescript import { useApi } from '@directus/extensions-sdk'; import { io, Socket } from 'socket.io-client'; const api = useApi(); let socket: Socket | null = null; function connectWebSocket() { // Get the API URL const baseURL = api.defaults.baseURL || window.location.origin; socket = io(baseURL, { transports: ['websocket'], auth: { access_token: api.defaults.headers.common['Authorization']?.replace('Bearer ', ''), }, }); socket.on('connect', () => { console.log('WebSocket connected'); subscribeToCollections(); }); socket.on('subscription', handleRealtimeUpdate); } function subscribeToCollections() { if (!socket) return; socket.emit('subscribe', { collection: 'articles', query: { fields: ['*'], filter: { status: { _eq: 'published' } }, }, }); } function handleRealtimeUpdate(data: any) { // Handle real-time updates if (data.action === 'create') { // New item created } else if (data.action === 'update') { // Item updated } else if (data.action === 'delete') { // Item deleted } } ``` ## Theme Integration ### Using Theme Variables ```css /* Available theme variables */ .my-component { /* Colors */ color: var(--theme--foreground); background: var(--theme--background); border-color: var(--theme--border-color); /* Primary colors */ background: var(--theme--primary); background: var(--theme--primary-background); background: var(--theme--primary-subdued); /* Semantic colors */ color: var(--theme--success); color: var(--theme--warning); color: var(--theme--danger); /* Spacing */ padding: var(--spacing-s); margin: var(--spacing-m); gap: var(--spacing-l); /* Border radius */ border-radius: var(--theme--border-radius); /* Typography */ font-family: var(--theme--fonts--sans--font-family); font-size: var(--theme--fonts--sans--font-size); /* Shadows */ box-shadow: var(--theme--shadow); } /* Dark mode support */ @media (prefers-color-scheme: dark) { .my-component { background: var(--theme--background-accent); } } ``` ## Using Directus Component Library ### Import Components ```vue ``` ### Available Components - **Forms**: v-input, v-textarea, v-select, v-checkbox, v-radio - **Buttons**: v-button, v-button-group, v-icon-button - **Feedback**: v-notice, v-dialog, v-tooltip, v-progress-circular - **Layout**: v-card, v-divider, v-tabs, v-drawer - **Data**: v-table, v-pagination, v-chip - **Navigation**: v-breadcrumb, v-menu, v-list ## Mobile Responsive Design ### Responsive Grid System ```vue ``` ## Testing Extensions ### Unit Testing with Vitest ```typescript // test/panel.test.ts import { describe, it, expect, beforeEach, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import PanelComponent from '../src/panel.vue'; // Mock Directus SDK vi.mock('@directus/extensions-sdk', () => ({ useApi: () => ({ get: vi.fn().mockResolvedValue({ data: [] }), post: vi.fn(), }), useStores: () => ({ useItemsStore: () => ({ getItems: vi.fn().mockResolvedValue([]), }), }), })); describe('Analytics Panel', () => { let wrapper; beforeEach(() => { wrapper = mount(PanelComponent, { props: { collection: 'test_collection', dateField: 'created_at', height: 400, width: 600, }, }); }); it('renders loading state initially', () => { expect(wrapper.find('.loading-state').exists()).toBe(true); }); it('displays metrics after data loads', async () => { // Wait for async operations await wrapper.vm.$nextTick(); await new Promise(resolve => setTimeout(resolve, 100)); expect(wrapper.findAll('.metric-card').length).toBeGreaterThan(0); }); it('handles error state gracefully', async () => { wrapper.vm.error = 'Test error'; await wrapper.vm.$nextTick(); expect(wrapper.find('.error-state').exists()).toBe(true); expect(wrapper.text()).toContain('Test error'); }); }); ``` ## Deployment ### Build Process ```bash # Development npm run dev # Production build npm run build # Output structure dist/ ├── index.js # Compiled extension ├── index.css # Styles (if any) └── package.json # Extension metadata ``` ### Installation Methods 1. **Via NPM**: ```bash npm install directus-extension-my-panel ``` 2. **Via Extensions Folder**: ```bash # Copy to extensions directory cp -r dist/ /directus/extensions/panels/my-panel/ ``` 3. **Via Admin Panel**: - Navigate to Settings → Extensions - Upload the .tar.gz package ## Best Practices ### Performance Optimization 1. **Use computed properties** for derived state 2. **Implement virtual scrolling** for large lists 3. **Debounce API calls** for search/filter inputs 4. **Lazy load** heavy components 5. **Cache API responses** when appropriate 6. **Use Web Workers** for heavy computations ### Code Organization ``` extension/ ├── src/ │ ├── components/ # Reusable components │ ├── composables/ # Shared logic │ ├── utils/ # Helper functions │ ├── types/ # TypeScript types │ ├── index.ts # Extension entry │ └── panel.vue # Main component ├── test/ │ └── *.test.ts # Test files ├── package.json └── tsconfig.json ``` ### Error Handling ```typescript // Comprehensive error handling try { const response = await api.get('/items/collection'); // Process response } catch (error) { // User-friendly error messages if (error.response?.status === 403) { showNotification({ type: 'error', title: 'Permission Denied', description: 'You don\'t have access to this resource', }); } else if (error.response?.status === 404) { showNotification({ type: 'warning', title: 'Not Found', description: 'The requested resource was not found', }); } else { showNotification({ type: 'error', title: 'Error', description: error.message || 'An unexpected error occurred', }); } // Log for debugging console.error('[Extension Error]:', error); } ``` ## Troubleshooting Guide ### Common Issues and Solutions 1. **Extension not loading** - Check `id` uniqueness in index.ts - Verify build output in dist/ - Check browser console for errors - Ensure Directus version compatibility 2. **API calls failing** - Verify user permissions - Check API endpoint paths - Validate authentication tokens - Review CORS settings 3. **Styling issues** - Use Directus theme variables - Check CSS scoping - Test in light/dark modes - Verify responsive breakpoints 4. **Performance problems** - Profile with Vue DevTools - Check API query efficiency - Implement pagination - Optimize reactive dependencies ## Success Metrics - ✅ Extension loads without errors - ✅ Data fetches and displays correctly - ✅ Real-time updates work (if implemented) - ✅ Mobile responsive design functions - ✅ Theme integration is seamless - ✅ Performance is smooth (< 100ms interactions) - ✅ Error states are handled gracefully - ✅ Accessibility standards are met - ✅ TypeScript types are properly defined - ✅ Unit tests pass with > 80% coverage ## Resources - [Directus Extensions SDK Documentation](https://docs.directus.io/extensions/introduction.html) - [Vue 3 Composition API](https://vuejs.org/guide/extras/composition-api-faq.html) - [Directus Component Library Storybook](https://components.directus.io/) - [TypeScript Vue Support](https://vuejs.org/guide/typescript/overview.html) - [Vite Configuration](https://vitejs.dev/config/) - [Chart.js Documentation](https://www.chartjs.org/docs/) ## Version History - **1.0.0** - Initial release with comprehensive Vue 3 extension patterns