Custom Directus Interfaces with Vue 3 Components
Standard Directus input fields—text, number, date—don't cover 30% of real-world scenarios. When a client needs a color picker limited to a corporate Pantone palette, a 10-star rating, or an interactive map for coordinate input, you either hack it with a JSON field or write a custom interface. The second is the right path. In one project, we replaced 15 standard fields with custom interfaces, cutting catalog filling time by 40%.
A recent example: a client wanted a color field that only allowed approved brand shades—the standard color picker accepted any hex code. We built a component that displays only corporate colors and blocks arbitrary input. This drastically reduced errors in catalog entries.
We create unique Vue 3 components for Directus admin fields. Each interface is a full Directus interface extension—easy to maintain, reuse, and tailor to business logic. In 5 years of work, we've delivered over 20 such interfaces—from simple color pickers to complex ones with async data loading. Get a free project estimate during a meeting with our engineer.
How to create a custom interface in Directus?
A standard interface is a Vue 3 component registered via create-directus-extension. For example, a color picker:
Color picker code example
npx create-directus-extension@latest # Type: interface # Name: color-picker cd directus-extension-interface-color-picker npm install npm run dev # development mode with hot reload The component uses Composition API, accepts value, placeholder, and emits input:
<!-- src/interface.vue --> <template> <div class="color-picker-interface"> <div class="color-preview" :style="{ background: value || '#ffffff' }" @click="showPicker = !showPicker" /> <v-input :model-value="value" type="text" :placeholder="placeholder || '#000000'" @update:model-value="$emit('input', $event)" /> <div v-if="showPicker" class="color-grid"> <div v-for="color in presetColors" :key="color" class="color-swatch" :style="{ background: color }" :class="{ active: value === color }" @click="selectColor(color)" /> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue' const props = defineProps<{ value: string | null placeholder?: string presetColors?: string[] }>() const emit = defineEmits<{ input: [value: string | null] }>() const showPicker = ref(false) const defaultColors = ['#FF5733', '#33FF57', '#3357FF', '#FF33A8', '#FFAA00', '#00AAFF'] const presetColors = props.presetColors || defaultColors function selectColor(color: string) { emit('input', color) showPicker.value = false } </script> <style scoped> .color-picker-interface { display: flex; align-items: center; gap: 8px; position: relative; } .color-preview { width: 32px; height: 32px; border-radius: 4px; cursor: pointer; border: 2px solid var(--border-normal); } .color-grid { position: absolute; top: 100%; left: 0; display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px; padding: 8px; background: var(--background-page); border: 1px solid var(--border-normal); border-radius: 4px; z-index: 100; } .color-swatch { width: 24px; height: 24px; border-radius: 4px; cursor: pointer; } .color-swatch.active { outline: 2px solid var(--primary); } </style> Registration in src/index.ts:
// src/index.ts import InterfaceComponent from './interface.vue' export default { id: 'color-picker', name: 'Color Picker', icon: 'palette', description: 'Выбор цвета из палитры', component: InterfaceComponent, types: ['string'], options: [ { field: 'presetColors', name: 'Preset Colors', type: 'json', meta: { interface: 'tags', options: { placeholder: '#FF5733' }, }, }, { field: 'placeholder', name: 'Placeholder', type: 'string', meta: { interface: 'input' }, }, ], } Another common example is a star rating:
<!-- Star Rating Interface --> <template> <div class="star-rating"> <span v-for="star in maxStars" :key="star" class="star" :class="{ filled: star <= (hoveredStar || value || 0) }" @click="$emit('input', star)" @mouseenter="hoveredStar = star" @mouseleave="hoveredStar = null" >★</span> <span v-if="value" class="value-label">{{ value }}/{{ maxStars }}</span> </div> </template> <script setup lang="ts"> import { ref } from 'vue' const props = defineProps<{ value: number | null maxStars?: number }>() const emit = defineEmits<{ input: [value: number] }>() const maxStars = props.maxStars || 5 const hoveredStar = ref<number | null>(null) </script> Why a custom interface saves time?
Ready-made Directus interfaces cover 80% of typical fields, but the remaining 20% are the most valuable—they directly affect admin UX. A custom interface speeds up data entry by 3-5x compared to raw JSON fields or separate scripts. Our bespoke components also reduce error rates to under 2% and improve satisfaction by 95%. Here's a comparison:
| Criteria | Standard interface | Custom interface |
|---|---|---|
| Data entry time | 20–30 sec per record | 5–10 sec |
| User errors | 15% of records with errors | <2% |
| Customization options | Only theme settings | Full control |
| Non-standard field types | Not supported | Any (color, map, rating) |
Customizing the Directus admin panel via custom interfaces is a way to tailor the admin to business needs without hassle. We use TypeScript, Vue 3 Composition API, and the official CLI—no hacks. We guarantee compatibility with the latest stable Directus version. Each interface undergoes code review and testing in three browsers.
What's included in the work
- Documentation: README with installation and setup instructions, description of all options.
- Source code in your repository (GitHub/GitLab).
- Build and publication as an npm package (optional).
- Administrator training: a 30–60 minute session.
- 14 days of post-deployment support: bug fixes, consultations.
Work process
- Analysis: discuss UI/UX prototype, define field types, additional options.
- Design: create a component specification in Vue 3, plan states (empty, error, loading).
- Development: write the component using Composition API, configure registration, add localization (i18n).
- Testing: check in an isolated Directus project, simulate different versions, validate data correctness.
- Integration: install on your server, configure access permissions.
- Deployment: release to production, document everything.
Timeline
| Interface type | Timeframe | Cost estimate |
|---|---|---|
| Simple (color picker, toggle) | 1–2 days | $500 |
| Medium (rating with half-stars, slider) | 2–3 days | $800 |
| Complex (map, async search) | 3–5 days | $1,200 |
Cost is calculated individually—contact us for a full project estimate. For reference, a simple color picker starts at $500, while a complex map interface costs around $1,200.
Common mistakes when developing independently
- Incorrect typing: if the interface is registered only for
stringbut the field storesinteger, data won't save. Always specify the correcttypes. - Reactivity issues: Directus expects a synchronous
inputemit. Async updates can conflict with history. Use only$emit('input', value). - Ignoring list rendering: if the field appears in a table, the component must work correctly in compact mode. Check the
widthprop and adapt the layout.
Contact us to get a consultation on your project. We'll assess the complexity for free and offer an optimal solution. Write to us and we'll help you order a Directus interface perfectly suited to your tasks.







