From nldd
Bouw applicaties met de web components van het NLDD Design System (@nldd/design-system, Nederlandse Digitale Dienst, Rijksoverheid). Triggers: @nldd/design-system, 'nldd-' tags, vragen over layout, sheets, popovers, modals, formulieren, toegankelijkheid, CSS-tokens of upgraden van dit systeem. NIET voor het ontwikkelen van het design system zelf (daarvoor: /component, /css).
How this skill is triggered — by the user, by Claude, or both
Slash command
/nldd:nlddThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Je gebruikt deze skill als je een **applicatie** bouwt bovenop
Je gebruikt deze skill als je een applicatie bouwt bovenop
@nldd/design-system: de web component-bibliotheek van de Nederlandse
Digitale Dienst (Rijksoverheid). Ben je bezig met het ontwikkelen van het
design system zelf (nieuwe componenten, CSS-conventies), gebruik dan
/component en /css, niet deze skill.
Twee bestanden horen hierbij:
reference.md: gegenereerde snelreferentie van elk
nldd-* element met zijn attributen, slots en events.examples/: werkende bootstrap- en patroonvoorbeelden voor
platte HTML, Vue 3, layout-patronen en een complete content-pagina.De levende documentatie met visuele voorbeelden staat in
Storybook. De exacte types staan in de
.d.ts bestanden van het pakket. Gebruik die twee als bron van waarheid voor
detailvragen; deze skill leert je hoe je het systeem goed gebruikt.
Het uitgangspunt van dit systeem is dat een ontwikkelaar de Rijkshuisstijl, de
toegankelijkheidseisen en het interactiegedrag van een overheidsinterface niet
uit het hoofd hoeft te kennen. Die regels zitten ingebakken in de componenten.
Wie een nldd-button plaatst, krijgt het juiste focusgedrag, de juiste
kleurcontrasten, de juiste ARIA en het juiste toetsenbordgedrag mee, zonder er
iets voor te doen. Toegankelijkheid en huisstijl worden zo gedrag in plaats van
kennis die in iemands hoofd moet zitten.
Dat heeft één belangrijke consequentie voor jou: als je tegen een component vecht, gebruik je het verkeerd. De componenten dragen opzettelijk meningen. Werk ermee mee.
Begin bij de inhoud. Navigatie, werkbalken en koppen zijn er om de inhoud te dienen, niet andersom. Voeg ze toe wanneer ze een taak ondersteunen, niet als standaard.
nldd-page ondersteunt een sticky header
(sticky-header) en meet zelf de hoogte zodat de inhoud er niet onder
schuift, maar de standaard is: geen sticky. Kies je er toch voor, verantwoord
dan waarom.design-guidelines.md.Het systeem verbergt secundaire inhoud zelf wanneer de ruimte krap wordt. Leun daarop in plaats van ertegen te werken.
nldd-side-by-side-split-view
en nldd-navigation-split-view stellen automatisch in welke panelen verborgen
worden als ze niet meer passen. Links = hoogste prioriteit. Plaats het
belangrijkste paneel (de hoofdinhoud) links, secundaire panelen (inspector,
detail) rechts. Die verdwijnen dan eerst.Gebruik componenten zoals ze zijn en stuur ze via attributen en tokens. Reik niet in de shadow DOM, override geen interne ARIA, plak geen klassen op childcomponenten.
nldd-banner zegt het
zelf in zijn documentatie: "if you need a quieter component, pick a different
one rather than overriding the banner's ARIA."Het systeem vervangt native elementen niet, het verpakt ze. Dat geeft je de volledige browser-toegankelijkheid en formulierafhandeling gratis.
nldd-dropdown is een visuele schil om een native <select>. Geef een
echte <select> als slotted child; de browser houdt de controle over
toetsenbord, formulierwaarde en toegankelijkheid.nldd-rich-text met een
rauwe <a>, niet nldd-link. nldd-link is voor UI-navigatie en
actiegebieden, niet voor lopende tekst.npm install @nldd/design-system
Importeer de componenten en de stijlen één keer, bij het opstarten van je app:
import '@nldd/design-system'; // registreert alle nldd-* componenten
import '@nldd/design-system/styles'; // CSS-tokens + Rijksoverheid-fonts
Voor tree-shaking kun je ook per component importeren via de subpath-export
(bijv. @nldd/design-system/button). Frameworks die templates compileren,
moeten nldd-* als custom elements herkennen (in Vue: isCustomElement).
De complete setups, inclusief de Vue-config en het per-component importeren,
staan in examples/bootstrap-html.md en
examples/bootstrap-vue.md.
Alle visuele waarden komen uit CSS-variabelen; niets is hardcoded. Dat maakt
licht/donker-thema's mogelijk via light-dark() en houdt je app in de
huisstijl. De variabelen zijn gelaagd:
| Laag | Prefix | Voor jou als consument |
|---|---|---|
| Primitives | --primitives-* | Basiswaarden (kleur, spacing, typografie). Gebruik voor je eigen styling rond de componenten. |
| Semantics | --semantics-* | Betekenisvolle rollen (knoppen, controls, oppervlakken). Bruikbaar, maar primitives volstaan meestal. |
| Components | --components-* | Component-specifiek. Zelden nodig in app-code. |
| Context | --context-* | Communicatie tussen componenten (bijv. achtergrondkleur die doorcascadeert). |
| Lokaal | --_* | Intern aan een component. Raak deze niet aan. |
Voor je eigen CSS (de ruimte tussen en rond componenten) gebruik je
--primitives-* voor spacing en kleur, en light-dark() voor themabewuste
waarden:
.mijn-rij {
display: flex;
gap: var(--primitives-space-8);
color: light-dark(
var(--primitives-color-neutral-700),
var(--primitives-color-neutral-300)
);
}
Licht en donker. Het hele palet is gebouwd op light-dark(), dus het thema
volgt de CSS color-scheme. Standaard is dat de OS- of browservoorkeur. Wil je
licht of donker forceren, zet dan color-scheme: light (of dark) op een
root-element; gebruik light-dark() zoals hierboven voor je eigen kleuren zodat
ze meebewegen. Er is geen aparte thema-toggle-API op nldd-app-view.
Elk patroon heeft een reden. De voorbeelden zijn gedestilleerd uit regelrecht, de productie-app die dit systeem het meest volwassen gebruikt.
nldd-app-view is altijd de buitenste schil: die zet de kleurschema-context en
de fonts. Wat erbinnen komt, hangt af van wat je bouwt. Er zijn twee
compositievormen, kies bewust:
| Vorm | Wanneer | Bouwstenen |
|---|---|---|
| App-shell | Applicaties met panelen: editors, dashboards, werkomgevingen. | nldd-app-view → split view → nldd-split-view-pane → nldd-page |
| Content-pagina | Landings-, marketing- of informatiepagina's: een verticale stapel inhoud. | nldd-app-view → nldd-page → *-section blokken → nldd-page-footer |
App-shell loopt van buiten naar binnen via split views:
<nldd-app-view>
<nldd-side-by-side-split-view panes="2">
<div slot="pane-1"><!-- hoofdinhoud, hoogste prioriteit --></div>
<div slot="pane-2"><!-- inspector, verdwijnt eerst op smal scherm --></div>
</nldd-side-by-side-split-view>
</nldd-app-view>
Waarom: de split view regelt de responsive auto-hide. Zet de prioriteit goed door de volgorde van de panelen.
Content-pagina is een stapel page-sections, geen split views:
<nldd-app-view>
<nldd-page>
<nldd-simple-section><!-- hero --></nldd-simple-section>
<nldd-simple-section>
<nldd-collection layout="grid"
item-width="320px"
>
<nldd-card><!-- ... --></nldd-card>
</nldd-collection>
</nldd-simple-section>
<nldd-page-footer><!-- ... --></nldd-page-footer>
</nldd-page>
</nldd-app-view>
Waarom: de *-section componenten (nldd-simple-section,
nldd-two-thirds-one-third-section, en de andere page-sections) regelen
responsive padding en kolom-wrapping zelf via container queries. Grids van
gelijkwaardige items bouw je met nldd-collection + nldd-card, niet met eigen
CSS-grid. Het volledige patroon staat in
examples/content-page.md.
Dit zijn geen uitwisselbare overlays. Elk heeft een doel:
| Surface | Gebruik voor | Niet voor |
|---|---|---|
nldd-sheet | Secundaire inhoud die context behoudt: formulieren, bewerk-oppervlakken, detail. Schuift in vanaf de zijkant (onderkant op mobiel). | Korte bevestigingen. |
nldd-modal-dialog | Het uiterste geval: een onomkeerbare actie waar geen veiliger weg omheen is. Onderbreekt bewust. | Data-invoer, complexe formulieren, of bevestigingen die ook met undo kunnen. |
nldd-popover | Lichte, niet-blokkerende panelen verankerd aan een trigger: filters, snelacties, zoekvelden. Sluit bij Esc en klik-buiten. | Inhoud die de hele aandacht vraagt. |
Vuistregel: secundaire inhoud op een smal scherm hoort in een sheet, niet in een modal. Een modal onderbreekt; reserveer dat voor momenten die een onderbreking verdienen.
Wanneer is een modal überhaupt gerechtvaardigd, en wat is het primary-label in een bevestiging? Dat zijn ontwerpkeuzes, geen component-mechaniek. De voorkeur is undo boven confirm en een contextueel-window (popover) boven een modal; zie
design-guidelines.md("Feedback en state").
Deze surfaces stellen show() en hide() beschikbaar als methoden. Spiegel je
toestand naar die calls in plaats van het element te mounten/unmounten, zodat de
animatie speelt. Spiegel óók de andere kant op, anders krijg je een
hide() → @close → hide() lus.
// Vue, vereenvoudigd uit regelrecht
watch(() => props.open, async (open) => {
if (!open) { sheetEl.value?.hide(); return; }
await nextTick();
sheetEl.value?.show();
}, { immediate: true });
Waarom: mount/unmount slaat de in- en uit-animatie over en verliest DOM-toestand. De imperatieve methoden animeren wel.
Belangrijk: dit fragment toont alleen de watch-kant. De sheet sluit zichzelf
bij Esc of klik-buiten en vuurt dan close; koppel @close aan een
emit('close') die diezelfde open-state omlaag zet, niet aan een directe
hide(). Anders krijg je de hide() → @close → hide() lus. Het complete,
werkende component staat in examples/bootstrap-vue.md.
Bouw rijen op uit cellen binnen een nldd-list-item. Niet uit losse divs.
<nldd-list variant="simple">
<nldd-list-item size="md" button>
<nldd-text-cell text="Titel" supporting-text="Ondertitel"></nldd-text-cell>
</nldd-list-item>
</nldd-list>
Beschikbare cellen: nldd-text-cell, nldd-icon-cell, nldd-title-cell,
nldd-description-cell, nldd-spacer-cell, en meer (zie reference.md).
nldd-form-field koppelt label en input automatisch (geen for/id-gedoe).
Voor een foutmelding zet je twee dingen op de input zelf: invalid en
error-message met de id('s) van de bijbehorende
nldd-form-field-error-text-elementen. Die wijzen zichzelf toe aan de juiste
slot; jij stuurt alleen invalid aan vanuit je eigen validatielogica.
<!-- zet `invalid` op de input als je validatie faalt -->
<nldd-form-field label="KvK-nummer">
<nldd-text-field name="kvk"
invalid
error-message="kvk-error"
></nldd-text-field>
<nldd-form-field-error-text id="kvk-error">
Vul een geldig KvK-nummer in (8 cijfers).
</nldd-form-field-error-text>
</nldd-form-field>
invalid is een gewone boolean-attribuut dat je dynamisch zet: in Vue
:invalid="hasError", in platte JS field.toggleAttribute('invalid', hasError).
Zo koppel je dezelfde vlag aan je validatie bij blur, submit of een
server-respons.
Waarom dit patroon: de koppeling loopt via error-message → id, niet via
shadow-DOM-trucs, zodat screenreaders de fout aan het veld koppelen. De
validatie-regels (wanneer is iets fout) zijn aan jou; het systeem regelt
alleen de presentatie en de toegankelijke koppeling.
Ontwerpkeuzes rond formulieren (markeer optionele velden in plaats van
verplichte, volg de gedachtegang van de gebruiker in de vraagvolgorde, één veld
voor de volledige naam) staan in design-guidelines.md
("Invoer en formulieren"). Het optional-attribuut op nldd-form-field toont
daarbij zelf de "Optioneel"-badge.
event.detailComponenten leveren hun waarde in event.detail, niet altijd op
event.target.value. Lees defensief:
function onInput(event) {
const value = event.detail?.value ?? event.target?.value;
// ...
}
Moet je echt bij een native input (bijvoorbeeld om te focussen)? Zoek dan met een fallback, zodat je code blijft werken als de interne structuur verandert:
const field = root.querySelector('nldd-search-field');
const native =
field?.shadowRoot?.querySelector('input') ?? field?.querySelector('input');
native?.focus();
Dit is een ontsnappingsluik. Gebruik het spaarzaam.
nldd-spacer versus nldd-containernldd-container voor padding rond een regio en de layout van zijn
kinderen (stack, rij, grid), met responsive sm- / md- / lg- varianten.nldd-spacer voor een kale verticale of horizontale ruimte tussen
opeenvolgende, verschillende componenten. Ook per breakpoint instelbaar.De grenzen zijn: sm ≤ 640px, md 641–1007px, lg ≥ 1008px. Het pakket
exporteert deze waarden nog niet publiek, dus als je ze in JS nodig hebt
(bijvoorbeeld om een popover anders te positioneren), hardcode ze in sync met
deze bron. Bekende beperking: controleer bij een pakketupdate of er
inmiddels wel een export is.
De componenten leveren correcte ARIA, focusvolgorde, een zichtbare blauwe
focusring, forced-colors-ondersteuning en prefers-reduced-motion af. De
wettelijke lat is WCAG 2.1 AA (EN 301 549, verplicht onder het Besluit digitale
toegankelijkheid overheid). Wat het systeem voor je regelt:
for/id).aria-haspopup; jij houdt expanded
bij als de popup opent en sluit.role/aria-live op basis van variant. Niet overschrijven.Wat jij nog moet doen:
nldd-skip-link, "Direct naar de inhoud") en een
logische focusvolgorde in je eigen markup.accessible-labels waar je tekst weglaat (icon-only
knoppen, geslotte inhoud).Het systeem brengt versies uit als patches (semantic-release verhoogt het
patch-nummer bij elke feat, fix of breaking change). Het versienummer alleen
zegt dus niet of een upgrade veilig is; de changelog wel. Gebruik
changelog.md als je upgradepad.
Werkwijze bij het verhogen van je @nldd/design-system versie:
Breaking / Breaking Changes secties eerst. Die bevatten
concrete migratie-instructies: een verwijderd attribuut met zijn vervanger,
hernoemde tokens, gewijzigd gedrag. Een echt voorbeeld uit de changelog:
variant="box-on-tinted" op nldd-list is verwijderd, met als vervanger
<nldd-list variant="box" background="base">.Highlights, Added en Changed voor nieuwe componenten of
attributen die je oudere, omslachtigere code kunnen vervangen.reference.md of een attribuut, slot of
event in de doelversie bestaat zoals je verwacht. Die referentie hoort bij
exact deze release.Vuistregel: ga niet meer dan een handvol patches in één sprong omhoog zonder de
tussenliggende Breaking secties te lezen. Hernoemde CSS-tokens zijn de meest
gemiste val: je eigen thema-overrides verwijzen dan naar een naam die niet meer
bestaat, zonder foutmelding, alleen een stille terugval op de default.
.d.ts types in het pakket: de exacte, actuele API.reference.md: offline snelreferentie van alle elementen.changelog.md: de release notes per versie. Raadpleeg
dit als een attribuut, slot of gedrag pas vanaf een bepaalde versie bestaat,
of om te zien wat er sinds jouw versie is veranderd.design-guidelines.md: de interface- en
ontwerpvoorkeuren van het systeem (invoer en formulieren, navigatie, feedback
en state, microcopy, visuele hiërarchie, strategie). Dit is de canonieke bron
voor ontwerpkeuzes; raadpleeg het bij vormgeven, microcopy schrijven of een
UI reviewen. Deze SKILL.md beschrijft de component-mechaniek, de guidelines
beschrijven de keuzes erachter.Iconen. nldd-icon name="…" accepteert namen uit een vaste set. De
volledige lijst (iconen plus aliassen) staat onder "Iconen" in
reference.md; verzin geen naam, kies er een uit die set.
Deze skill gaat over het gebruiken van het design system: welke componenten, welke patronen, welke visie. Wat erbuiten valt en je zelf invult vanuit je applicatie- en frameworkkeuzes: state-management en validatieregels, server-side foutafhandeling, routing, en het testen van je eigen app. Voor SSR/hydratie geldt de algemene web-componentenpraktijk (de componenten upgraden client-side; render geen kritieke inhoud uitsluitend in hun shadow DOM). De componenten zelf zijn los getest binnen het design system; jouw app-tests schrijf je met je eigen testopstelling.
Voor onderhouders:
reference.md,changelog.mdendesign-guidelines.mdzijn gegenereerd (uit respectievelijk de JSDoc van de componenten, de root-CHANGELOG ensrc/docs/design-guidelines.mdx). Draainpm run generate:skill-docsna een API-wijziging, release of wijziging in de ontwerprichtlijnen en commit het resultaat. Het zijn echte bestanden, geen symlinks: een plugin wordt naar een geïsoleerde cache gekopieerd waarbij symlinks buiten de plugin-map wegvallen.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub minbzk/storybook --plugin nldd