Hyvä theming met Tailwind CSS: best practices voor developers
Terug naar blog

Hyvä theming met Tailwind CSS: best practices voor developers

AuthorRuthger Idema
14 juni 20269 min leestijd

Een Hyvä-shop laadt een CSS-bundle van 12 tot 20 kB. Een Luma-shop schiet richting 200 kB aan render-blocking CSS. Dat verschil komt grotendeels door Tailwind en de JIT-engine.

Hyvä theming met Tailwind CSS: best practices voor developers

Een Hyvä-shop laadt een CSS-bundle van 12 tot 20 kB. Een Luma-shop schiet richting 200 kB aan render-blocking CSS. Dat verschil komt grotendeels door Tailwind en de JIT-engine.

Maar dat resultaat is geen automatisme. Wij zien bij audits regelmatig Hyvä-shops met een CSS-bundle van 80 kB of meer. De oorzaak is bijna altijd dezelfde: verkeerd geconfigureerde purge, overrides op de verkeerde plek, of een child theme dat de hele parent opnieuw compileert.

Dit artikel gaat over hoe je dat voorkomt. Concreet: Tailwind-config, design tokens, JIT en purge, theme overrides via fallback, child themes en CSS-performance. Met do's en don'ts uit de praktijk.

Hoe Hyvä en Tailwind samenwerken

Hyvä compileert CSS niet via bin/magento. Het draait een eigen Tailwind-build vanuit de theme-directory.

De kern zit in app/design/frontend/{Vendor}/{theme}/web/tailwind/. Daar vind je:

  • tailwind.config.js — de configuratie, inclusief content paths en theme tokens.
  • tailwind-source.css — het entry-bestand met @tailwind base, components en utilities.
  • package.json — de build-scripts (npm run watch, npm run build-prod).

De build scant je .phtml-templates op gebruikte classes en genereert alleen die CSS. Geen template, geen class. Dat is het hele performance-verhaal in één zin.

Belangrijk: Tailwind in Hyvä is een dev-time tool. In productie draai je build-prod, commit je de gegenereerde styles.css en deploy je die. Je server hoeft geen Node te draaien.

Tijdens development draai je npm run watch. Die houdt je templates in de gaten en hercompileert bij elke wijziging. De feedback-loop is bijna instant: je slaat een .phtml op, ververst de browser en ziet je class direct toegepast. Geen setup:static-content:deploy, geen cache-flush. Dat scheelt dagelijks tientallen minuten ten opzichte van een Luma-workflow.

Eén valkuil: vergeet niet om vóór elke deploy build-prod te draaien in plaats van de development-build te committen. De development-build mist minificatie en weegt fors meer. Wij bouwen dit standaard in de CI-pipeline, zodat een developer het nooit handmatig kan vergeten.

Tailwind-config: content paths zijn alles

De meest gemaakte fout zit in content (vroeger purge). Als je content paths te breed zijn, blaast je bundle op. Te smal, en classes verdwijnen die je wél gebruikt.

Hyvä levert standaard een goede basis:

js
module.exports = {
  content: [
    '../../**/*.phtml',
    '../../*.phtml',
    './tailwind/components/**/*.{js,jsx}',
    '../../**/web/template/**/*.html',
  ],
  // ...
}

Die ../../*/.phtml scant je hele theme. Prima. Maar voeg je een module-frontend toe met eigen templates, dan moet je dat pad expliciet meenemen:

js
content: [
  '../../**/*.phtml',
  '../../../../../../app/code/Vendor/Module/view/frontend/templates/**/*.phtml',
],
Do: wijs content paths exact aan op directories met templates. Don't: zet er nooit node_modules of vendor volledig in. Je build-tijd loopt op naar minuten en je bundle vult zich met dode CSS.

Design tokens via theme.extend

Hardcode geen kleuren in je templates. Definieer design tokens in de config en gebruik die overal. Eén bron van waarheid.

js
theme: {
  extend: {
    colors: {
      primary: {
        DEFAULT: '#1f2937',
        lighter: '#374151',
      },
      accent: '#e11d48',
    },
    fontFamily: {
      sans: ['Inter', 'system-ui', 'sans-serif'],
    },
  },
}

Gebruik extend en niet de top-level theme. Top-level overschrijft Tailwind's complete default-set. Dan verlies je in één klap alle standaard spacing, kleuren en breakpoints.

Voor tokens die ook in runtime-context moeten werken (denk aan dark mode of een themeswitcher), koppel je Tailwind aan CSS custom properties:

js
colors: {
  primary: 'var(--color-primary)',
}

Zo stuur je het thema vanuit de CSS-laag aan zonder te herbouwen. Handig voor multi-store setups waar elke store-view een eigen kleurpalet heeft.

Houd je token-set bewust klein. Wij zien projecten met dertig kleurvarianten waarvan er in de praktijk acht gebruikt worden. Elke ongebruikte token die je via een class toch ergens neerzet, vergroot je bundle. Definieer alleen wat het design echt nodig heeft.

Een laatste tip over tokens: documenteer ze. Een design token zonder uitleg wordt na drie maanden alsnog hardcoded omdat niemand wist dat hij bestond. Een kort README in je theme-directory met de tokens en hun bedoeling voorkomt dat.

JIT en purge: waar de bundle-winst zit

Sinds Tailwind 3 is JIT (Just-In-Time) de standaard. Er bestaat geen aparte purge-stap meer. De engine genereert tijdens de build uitsluitend de classes die in je content paths voorkomen.

Dat heeft twee gevolgen voor je dagelijkse werk:

  1. Dynamische classnames breken. Tailwind scant statische strings. class="text--500" wordt niet herkend. De class belandt niet in de bundle.
  2. Arbitrary values werken wél. class="top-[117px]" genereert on-the-fly de juiste regel. Geen config nodig.

Voor punt 1 gebruik je een safelist of, beter, volledige classnames in een map:

php
$colorMap = [
    'red' => 'text-red-500',
    'blue' => 'text-blue-500',
];
echo $colorMap[$color];
Do: schrijf volledige, statische class-strings. Don't: bouw classnames met string-concatenatie. Dat is de nummer één reden dat styling "willekeurig" wegvalt na een productie-build.

Een safelist gebruik je alleen als laatste redmiddel, want elke safelist-entry zit gegarandeerd in je bundle, gebruikt of niet:

js
safelist: [
  { pattern: /bg-(red|green|blue)-(100|500|700)/ },
],

Theme overrides via het fallback-mechanisme

Hyvä draait op Magento's theme fallback. Je hoeft de parent niet te kopiëren om iets te wijzigen. Je override alleen het bestand dat je verandert.

Wil je de header aanpassen? Kopieer dan uitsluitend dat ene template naar je eigen theme:

app/design/frontend/Acme/shop/Magento_Theme/templates/html/header.phtml

Magento laadt jouw versie en negeert die van de parent. De rest van de Hyvä-theme blijft intact en blijft updatebaar.

Do: override per bestand, zo dicht mogelijk bij het origineel. Don't: kopieer nooit de complete parent-theme naar je child. Bij elke Hyvä-update moet je dan handmatig diffen. Dat kost dagen en je mist security-fixes.

Voor CSS volg je dezelfde logica. Voeg eigen utilities en components toe in je child-config of in een apart CSS-bestand dat via @layer in de build wordt opgenomen:

css
@layer components {
  .btn-cta {
    @apply bg-accent text-white px-6 py-3 rounded font-semibold;
  }
}

Het verschil met losse CSS is dat code in een @layer meegaat in de purge. Schrijf je CSS buiten een layer, dan blijft die altijd in de bundle staan, ook als geen enkel template de class gebruikt. Alles wat je toevoegt hoort dus in base, components of utilities.

Een veelvoorkomende fout: de override op de verkeerde laag plaatsen. Pas je een Hyvä-component aan via een template-override, maar laat je de bijbehorende styling in de parent staan, dan kunnen er specificiteit-conflicten ontstaan. Houd template-override en styling-override bij elkaar in hetzelfde theme.

Child themes opzetten zonder dubbele build

Een child theme erft van het Hyvä-default-theme via theme.xml:

xml
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <title>Acme Shop</title>
    <parent>Hyva/default</parent>
</theme>

De vraag die we het vaakst krijgen: waar draait de Tailwind-build? Het antwoord bepaalt je performance.

Er zijn twee patronen:

  • Eigen build in het child theme. Het child heeft zijn eigen tailwind/-directory en compileert een eigen styles.css. Volledige controle, maar je dupliceert configuratie.
  • Parent-build uitbreiden via tailwind.config.js import. Je child importeert de parent-config en breidt die uit met presets. Minder duplicatie, maar je build moet wel de templates van zowel parent als child scannen.
js
module.exports = {
  presets: [require('../../../../Hyva/default/web/tailwind/tailwind.config.js')],
  content: [
    '../../**/*.phtml',
    '../../../../Hyva/default/**/*.phtml',
  ],
}
Do: gebruik presets om tokens te delen en voeg de parent-templates toe aan je content paths. Don't: draai twee losse builds die twee CSS-bundles uitspugen. Dan laadt de browser dubbele utilities en is je performance-voordeel weg.

Voor de afweging wanneer een child theme loont versus een schone start, helpt onze pagina over Hyvä installeren op een bestaande Magento-shop.

Performance van de CSS-bundle bewaken

Een schone Hyvä-bundle zit onder 25 kB gzipped. Boven 40 kB is er iets mis. Meet, raad niet.

Controleer na elke build-prod de bundlegrootte:

bash
ls -lh web/css/styles.css
gzip -c web/css/styles.css | wc -c

De grootste boosdoeners die wij tegenkomen:

ProbleemEffect op bundleOplossing
Te brede content paths+30 tot 100 kBPaths exact afbakenen
Onnodige safelists+5 tot 20 kBSafelist minimaliseren
Top-level theme overridevolledige defaults erbijtheme.extend gebruiken
Dubbele child/parent builddubbele utilitieséén build met presets
Custom CSS buiten @layerniet ge-purgedAlles via Tailwind layers

Nog drie concrete wins:

  • Schakel ongebruikte core plugins uit. Gebruik je geen float of backdrop-filter? Zet ze uit via corePlugins. Scheelt direct gegenereerde regels.
  • Vermijd @apply voor losse, eenmalige stijlen. Het dupliceert utility-CSS. Voor componenten die je tien keer hergebruikt is @apply prima.
  • Laad alleen de fonts die je gebruikt. Een te zware webfont weegt zwaarder dan je hele CSS-bundle.

De winst van een strakke bundle is meetbaar in Core Web Vitals. Minder render-blocking CSS betekent een lagere LCP. Wij gaan dieper op die cijfers in onze performance-vergelijking tussen Hyvä en Luma.

Wanneer Tailwind in Hyvä níet de juiste keuze is

Eerlijk blijven: Tailwind past niet bij elk team.

Heb je een design-team dat in losse, gescheiden SCSS-bestanden wil werken met een eigen naming-conventie? Dan vecht je tegen de utility-first filosofie. Je kunt het forceren, maar dan verlies je het purge-voordeel en de snelheid.

Werk je met externe pagebuilders die hun eigen CSS injecteren? Dan krijg je conflicten tussen die CSS en je Tailwind-bundle. Reken op extra integratiewerk.

In de meeste e-commerce-cases wint Tailwind alsnog, juist door de Hyvä-architectuur eromheen. Maar het is geen wet. Twijfel je over de juiste theming-strategie voor jouw shop? Neem contact op en we kijken concreet mee naar je setup, build-pipeline en bundlegrootte.

Veelgestelde vragen

Moet ik Node.js op mijn productieserver draaien voor Hyvä?

Nee. Tailwind compileert tijdens development of in je CI-pipeline. Je draait npm run build-prod, commit de gegenereerde styles.css en deployt dat bestand. De productieserver serveert alleen statische CSS. Node is daar niet nodig.

Waarom verdwijnt mijn styling na een productie-build?

Bijna altijd door dynamisch opgebouwde classnames. De JIT-engine scant statische strings in je templates. Een class als text-{$kleur}-500 wordt niet herkend en belandt niet in de bundle. Gebruik volledige class-strings via een map, of als laatste redmiddel een safelist.

Hoe groot mag mijn Hyvä CSS-bundle zijn?

Een schone bundle zit onder de 25 kB gzipped. Tussen 25 en 40 kB is acceptabel voor complexe shops. Boven 40 kB is er bijna altijd een probleem met te brede content paths, dubbele builds of overbodige safelists.

Kan ik bestaande SCSS hergebruiken in een Hyvä-theme?

Beperkt. Je kunt custom CSS toevoegen via @layer, maar dan profiteer je niet van Tailwind's tooling. Grote SCSS-libraries één-op-één overzetten ondermijnt het performance-voordeel. Beter is het herschrijven naar utilities en components. Lees ook hoe wij Hyvä installeren op een bestaande shop.

Wat is het verschil tussen een child theme met eigen build en presets?

Een eigen build genereert een losse CSS-bundle per theme, wat tot dubbele utilities leidt. Met presets deel je de configuratie en compileer je één bundle die zowel parent- als child-templates scant. Presets zijn vrijwel altijd de betere keuze voor performance.

Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check