Hyvä UI levert tientallen kant-en-klare componenten, maar vroeg of laat wil je iets dat er niet tussen zit. Dan bouw je zelf een component in exact dezelfde stijl: Tailwind-classes, Alpine.js voor interactie, geen build-step voor JavaScript. Dit artikel laat zien hoe je dat doet, stap voor stap.
Custom componenten bouwen met Hyvä UI
Hyvä UI levert tientallen kant-en-klare componenten, maar vroeg of laat wil je iets dat er niet tussen zit. Dan bouw je zelf een component in exact dezelfde stijl: Tailwind-classes, Alpine.js voor interactie, geen build-step voor JavaScript. Dit artikel laat zien hoe je dat doet, stap voor stap.
Wat is Hyvä UI precies?
Hyvä UI is het officiële design system en de componentbibliotheek van het Hyvä-ecosysteem. Het is gebouwd op Tailwind CSS en Alpine.js, HTML-first, met minimale JavaScript.
Belangrijk onderscheid: Hyvä UI is niet hetzelfde als een Hyvä theme. Een Hyvä theme vervangt de volledige Magento 2 frontend (Luma, RequireJS, jQuery, Knockout). Hyvä UI is een losse componentbibliotheek. Je kunt hem gebruiken in Magento 2 met Hyvä theme, maar ook in Laravel, headless-setups of statische sites. Framework-agnostisch.
De bibliotheek bevat onder andere: buttons, forms, modals, dropdowns, accordions, navigatie, tabs en product cards. De prijs is eenmalig ongeveer 250 euro per-ontwikkelaar/projectlicentie, geen abonnement. Er is ook een Figma Community-bestand bijgeleverd: design tokens en componenten die 1-op-1 matchen met de code.
Meer achtergrond over het volledige design system lees je op onze Hyvä UI-pagina.
De filosofie achter een custom Hyvä UI-component
Voordat je begint: begrijp de twee regels die elk Hyvä UI-component volgt.
- HTML-first. Structuur en stijl zitten in de markup, niet in een JavaScript-module.
- Alpine.js alleen voor interactie. State, events en eenvoudige DOM-manipulatie. Niet voor data-fetching of business logic.
Als je die twee regels aanhoudt, gedraagt jouw custom component zich identiek aan de meegeleverde componenten. Dat is belangrijk voor consistentie in het team en voor performance.
Hyvä scoort ~95% minder JavaScript dan Luma. Dat vertaalt zich naar Lighthouse-scores van 95+. Die winst verdwijnt zodra je Alpine.js gaat gebruiken voor dingen die CSS al kan, of wanneer je een externe JavaScript-library inlaadt voor iets triviaals.
Stap 1: design tokens en Tailwind-config
Hyvä UI werkt met design tokens. Kleuren, spacing en typography zijn gedefinieerd in de Tailwind-config, niet hard-coded in de componenten. Controleer eerst of jouw tailwind.config.js de Hyvä-tokens bevat.
Een minimale setup ziet er zo uit:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.html',
'./src/**/*.php',
'./src/**/*.phtml',
],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#1a56db',
light: '#3f83f8',
dark: '#1e429f',
},
secondary: {
DEFAULT: '#0e9f6e',
light: '#31c48d',
dark: '#057a55',
},
},
fontFamily: {
sans: ['Inter', 'ui-sans-serif', 'system-ui'],
},
},
},
plugins: [],
}
Gebruik je het Figma Community-bestand, dan staan de exacte token-waarden daar in. Zo synchroniseer je design en code zonder handmatig overtikken.
Stap 2: de component structureren
We bouwen een notification banner: een opvallende balk die bovenaan de pagina verschijnt met een bericht, en die de gebruiker weg kan klikken. Simpel genoeg om te begrijpen, complex genoeg om de patronen te laten zien.
Componenten in Hyvä UI volgen altijd dezelfde structuur:
| Laag | Verantwoordelijkheid | Technologie |
|---|---|---|
| Markup | Structuur en semantiek | HTML |
| Stijl | Visuele presentatie | Tailwind CSS |
| Interactie | State en events | Alpine.js |
| Data | Inhoud en configuratie | PHP/Blade/template |
Zo ziet de component eruit:
<!-- Notification Banner Component -->
<div
x-data="notificationBanner({ autoClose: 5000 })"
x-show="visible"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-2"
role="alert"
class="relative flex items-center justify-between gap-4 px-4 py-3 text-sm font-medium text-white bg-primary rounded-lg shadow-md"
>
<!-- Icon -->
<div class="flex items-center gap-2 min-w-0">
<svg class="w-5 h-5 shrink-0" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd"
d="M18 10A8 8 0 1 1 2 10a8 8 0 0 1 16 0zm-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-1 9a1 1 0 0 1-1-1V9a1 1 0 1 1 2 0v4a1 1 0 0 1-1 1z"
clip-rule="evenodd" />
</svg>
<!-- Message slot -->
<p class="truncate" x-text="message"></p>
</div>
<!-- Close button -->
<button
@click="close()"
type="button"
class="shrink-0 p-1 rounded hover:bg-white/20 focus:outline-none focus:ring-2 focus:ring-white/50 transition-colors"
aria-label="Sluiten"
>
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 0 1 1.414 0L10 8.586l4.293-4.293a1 1 0 1 1 1.414 1.414L11.414 10l4.293 4.293a1 1 0 0 1-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L8.586 10 4.293 5.707a1 1 0 0 1 0-1.414z"
clip-rule="evenodd" />
</svg>
</button>
</div>
Stap 3: de Alpine.js component schrijven
De JavaScript-functie hoort in een apart -blok of in een los .js-bestand dat je inlaadt. In Magento 2 met Hyvä gebruik je doorgaans een .phtml-bestand of een dedicated JavaScript-bestand dat via require (Hyvä gebruikt geen RequireJS, maar een eigen module-loader) of direct als wordt ingeladen.
// notification-banner.js
function notificationBanner({ message = '', autoClose = 0 } = {}) {
return {
visible: true,
message: message || document.querySelector('[data-banner-message]')?.textContent || '',
init() {
if (autoClose > 0) {
setTimeout(() => this.close(), autoClose);
}
},
close() {
this.visible = false;
},
};
}
Dat is alles. Geen classes, geen bundler, geen dependency. De functie retourneert een Alpine.js data-object.
De init()-hook is Alpine-natief: die wordt aangeroepen zodra de component geïnitialiseerd is. Gebruik init() voor timers, event-listeners en data-fetching. Gebruik destroy() (ook Alpine-natief) om die timers en listeners op te ruimen als de component uit de DOM verdwijnt.
Stap 4: de component aanroepen
In de template geef je opties mee als argument aan de functie:
<!-- Gebruik in een template -->
<div data-banner-message>
Je bestelling #12345 is verzonden.
</div>
<div
x-data="notificationBanner({ message: 'Je bestelling #12345 is verzonden.', autoClose: 8000 })"
...
>
Of je leest de tekst uit een data-attribuut — handig als de content server-side wordt gegenereerd en je de Alpine-markup niet wilt vervuilen met PHP-output.
Meer voorbeelden van Alpine-patronen in Hyvä UI-stijl vind je in ons artikel over Alpine-componenten in Hyvä.
Stap 5: varianten met Tailwind
Een goede component ondersteunt varianten. Geen JavaScript voor varianten — dat doe je met Tailwind-classes via :class.
<div
x-data="notificationBanner({ type: 'success' })"
:class="{
'bg-primary text-white': type === 'info',
'bg-secondary text-white': type === 'success',
'bg-red-600 text-white': type === 'error',
'bg-yellow-400 text-gray-900': type === 'warning',
}"
class="relative flex items-center justify-between gap-4 px-4 py-3 text-sm font-medium rounded-lg shadow-md"
>
Update de Alpine-functie om type als parameter te accepteren:
function notificationBanner({ message = '', autoClose = 0, type = 'info' } = {}) {
return {
visible: true,
message,
type,
// ...
};
}
Vier varianten, nul extra JavaScript-logica voor de stijl. Dat is hoe Hyvä UI werkt.
Wanneer Hyvä UI niet de juiste keuze is
Hyvä UI is een uitstekende basis, maar niet voor elke situatie.
- Zware interactieve applicaties. Complexe SPA-functionaliteit (denk aan een uitgebreide filterlogica met honderden state-varianten) wordt knap onhandelbaar in puur Alpine.js. Dan kijk je beter naar een dedicated React- of Vue-component.
- Teams zonder Tailwind-kennis. Tailwind heeft een leercurve. Als jouw team daar niet mee werkt, kost Hyvä UI meer tijd dan het oplevert.
- Projecten die Tailwind niet kunnen/willen gebruiken. Hyvä UI is gebouwd op Tailwind. Je kunt de patronen overnemen zonder de bibliotheek, maar dan koop je eigenlijk iets wat je niet volledig benut.
- Legacy Magento 2-setups op Luma. Hyvä UI is niet bedoeld als drop-in vervanging voor Luma-widgets. Dan is een Hyvä theme-migratie de eerste stap.
Wij zien bij klanten dat de grootste valkuil is: Hyvä UI kopen, de componenten kopiëren, en dan toch JavaScript-zware oplossingen bouwen voor interactie die Alpine prima aankan. De winst zit in de discipline: HTML-first, Alpine alleen voor interactie.
Checklist: component klaar voor productie?
Voordat je een custom component inbrengt in de codebase:
- [ ] Tailwind-classes gebruiken design tokens uit de config, geen hard-coded kleuren
- [ ] Alpine-functie is een pure functie die een data-object retourneert
- [ ]
init()endestroy()zijn geïmplementeerd als er timers of event-listeners zijn - [ ] Component is keyboard-toegankelijk (focus-states,
aria--attributen,role) - [ ] Varianten worden afgehandeld via
:class, niet via JavaScript-stijlmanipulatie - [ ] Geen externe JavaScript-libraries geïmporteerd tenzij absoluut noodzakelijk
- [ ] Lighthouse-score gecontroleerd na integratie
Wat levert dit op?
Een custom Hyvä UI-component die je volgens bovenstaande aanpak bouwt:
- Past visueel naadloos in het design system
- Voegt geen extra JavaScript-weight toe
- Is herbruikbaar in Magento 2, Laravel of elke andere context
- Is onderhoudbaar door elke developer die Tailwind en Alpine kent
Wij zien bij klanten dat een goed gebouwde Hyvä UI-codebase na twee jaar nog steeds uitbreidbaar is zonder technische schuld. De reden is eenvoudig: geen proprietary framework-magie, alleen HTML, CSS en minimale JavaScript.
Wil je weten of Hyvä UI past bij jouw project, of twijfel je of een custom component de juiste aanpak is? Via /contact plan je een gratis Tech Check in. Geen verkooppraatje, gewoon een eerlijk gesprek over wat wel en niet werkt voor jouw situatie.

Geschreven door Ruthger Idema
15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.
Meer over ons team →