Update dependency @nldd/design-system to ^0.8.86 #341

Open
robbertbos wants to merge 1 commit from renovate/nldd into main
Owner

This PR contains the following updates:

Package Change Age Confidence
@nldd/design-system ^0.8.82^0.8.86 age confidence

Release Notes

MinBZK/storybook (@​nldd/design-system)

v0.8.86

Compare Source

Highlights
  • No 'unsafe-inline' in your style-src for our components. Two of them wrote styling into the page itself, which is exactly what a Content-Security-Policy stops: under a strict policy a cell hid at no width at all, and a progress circle lost the color of its ring, both without a word. The rules travel through the CSSOM now, which a policy leaves alone. What each one did is in Fixed below.

  • A favicon, as @nldd/design-system/favicon.svg. The package shipped the logo, the fonts, the reset and the tokens, and left the tab strip to every site to redraw. They drifted, down to two shades of the same blue. This is the arms filling the tile on the lintje #154273, the pattern logius.nl and MijnOverheid Zakelijk already use, and the one that survives 16 pixels where the ribbon turns to mush. Copy it into whatever you serve statically and point a <link rel="icon"> at it. A browser loads a favicon apart from the page, so your CSS cannot reach it: another background is a copy of the file with one fill changed, which is what the sites that put their own house color around it are doing anyway. There is a PNG of the same tile beside it, @nldd/design-system/touch-icon.png, because Safari will not take an SVG for the icon on the home screen. It is 180 square, the size Apple asks for, and it is one file rather than a ladder of sizes. This Storybook now wears both.

  • On a narrow screen the wordmark beside the ribbon is centred against it. It started at the ribbon's midpoint and grew down from there, so a one-line wordmark hung against the bottom half of the mark with the space above it empty. It is centred now, and only what does not fit grows downward: centring a taller wordmark would push its first line off the top of the page, which is where the ribbon starts. Once it does grow it keeps 12px from that edge, and nothing under the last line, so the bar is no taller than the text in it. An auto margin does the work, so there is no height to guess and no breakpoint to keep in step with the text. Above sm nothing changes.

Breaking
  • A CSS length in gap on nldd-collection no longer applies. It was the only component that took one. Use the step with the same number: gap="16px" becomes gap="16". A gap that has to change with the viewport is sm-gap / md-gap / lg-gap rather than a length.

  • --context-layer-top and --context-layer-bottom are now --context-inset-top and --context-inset-bottom. The value says how much of the edge is taken by chrome that a sticky element has to clear, and layer reads as stacking order, which is what z-index and @layer are for. Inset is the word the platform already uses for this, as in env(safe-area-inset-top) and Android's window insets, and the same number is what scroll-padding-top wants, which has nothing sticky about it. Rename it wherever your app publishes or reads it. A CSS variable that no longer exists fails without a word, so nothing warns: sticky content lands against the top edge instead of below your bar. nldd-app-view, nldd-page, nldd-sheet, nldd-bar-split-view and nldd-modal-dialog all speak the new name.

Added
  • gap is a step of the spacing scale in both components, and so is padding in nldd-container. nldd-collection read gap as a CSS length and nldd-container read it as a scale step, so gap="16" was right in one and wrote --_gap: 16 in the other, which is not a length: the declaration fell away and the space between the items became nothing at all, without a word. One meaning now, behind one resolver. Spacing is rhythm, so a value beside the scale is not a finer choice but a break in the pattern, and a length is refused like any other value that cannot be placed: nothing is written, the default stands, and dev names the value rather than leaving you to spot the collapse. nldd-collection gains sm-gap, md-gap and lg-gap on the way, which nldd-container already had and which is where a gap that has to vary belongs.

  • layout="lanes" on nldd-collection. Items of unequal height packed into columns, the masonry nldd-container has had for a while, now on the component that pages. item-width is the minimum column width, the same as it is in grid. Where the browser has grid-lanes it uses it; where it does not it falls back to that grid rather than to the multicol nldd-container falls back to. That is the whole reason the two differ: multicol fills column by column and redistributes every item each time show-load-more adds to the set, so what you had already read moves. Grid fills row by row, like native lanes, and grows at the bottom. What you give up in the fallback is the ragged edge, which is a look rather than a behaviour.

  • A row can be a radio: radio on nldd-list-item, type="radiogroup" on nldd-list. A choice out of a handful used to mean an nldd-radio-button-group, which gives every option a label and nothing else. As rows the options can carry what a filter list wants beside them, a count that lines up on the right and reads as secondary, and the whole row is the target instead of the dot. The row is the control, the way checkbox already was: the action becomes a role="radio" button with aria-checked, activation sets checked and never clears it, and change fires once, the way a native radio stays put when you pick what was already picked. Slot an nldd-radio-button decorative for the shape. The group is the parent: type="radiogroup" makes the items region a role="radiogroup" and the rows step out of the tree with role="none", so the radios are the group's own children rather than list items wrapped around them.

Changed
  • A sticky fade is 24px, and a sidebar keeps 24px from the edges. The fade under nldd-page's sticky header and above its sticky footer was 32px, and the default sticky inset of nldd-sidebar-section was 16px, so a box that cleared the header still began inside its fade. Both are 24px now, which is what lets the sidebar start where the fade ends. Nothing to set for it, and sticky-top / sticky-bottom still override.
Fixed
  • An empty list and a list that found nothing are two states now, and [slot="no-results"] is the second one. They used to be one: [slot="empty"] said the same sentence to someone who has no assets yet and to someone whose search matched none of their twenty. The list can tell them apart, because it knows both how many rows it has and how many are [hidden], so it now picks the one that fits and falls back to empty when no-results is not given.

    What changes with it is the chrome. Rows that are only filtered away keep the search field and the [slot="toolbar"], because those are the way back to the rows: since 0.8.84 a query that matched nothing took the whole list off the page, search field and filters included, leaving no way back at all. No rows at all hides them instead, since there is nothing to search or filter, and with nothing in [slot="empty"] the list still leaves the page entirely. A list that fetches its rows is in that state until they arrive: put an nldd-inline-dialog variant="loading" in empty to hold the place, rather than have the controls appear a moment later. And a slot with nothing in it draws no box any more, where an empty surface read as a skeleton that never loaded.

  • A long word in the wordmark no longer shoves the ribbon off centre. A grid item is at least as wide as its longest word unless it is told otherwise, and the wordmark sits in one of the two tracks that hold the ribbon in the middle. One unbreakable name pushed the ribbon 137px to the left and the page 43px past the screen, so a phone got a horizontal scrollbar on the bar alone. The name wraps mid-word now, with overflow-wrap: anywhere rather than break-word, because only that one counts the break when the track is measured. Wrapped and not truncated: a name cut off by an ellipsis cannot be read at all.

  • The wordmark measures itself against the ribbon of the breakpoint it is in. --_logo-width was declared per breakpoint inside the logo element itself, so everything outside it kept reading the small value: on lg the wordmark reserved 80px beside a 96px ribbon and the spacer that places it was 8px short. The declaration sits on the bar now, where the ribbon and the wordmark both read it.

  • Sticky content inside a page clears the page's own bars. nldd-page said nothing about its sticky header, so an nldd-sidebar-section in one stuck to the top of the viewport and slid under it: the top of the sidebar sat behind the bar, and the height cap was a whole viewport where a viewport minus the bars was left, which ran the bottom of the box off the screen. The page now adds its sticky header and footer to --context-inset-top and --context-inset-bottom for its content, the same variables the shell already uses to tell the page where it may stick. nldd-sidebar-section reads them, so its box lands 24px below whatever is above it and ends 24px above whatever is below, the depth of the fade a sticky header casts, so it starts where that fade ends, and sticky-top / sticky-bottom go back to being for chrome the page cannot know about. The heights are measured rather than assumed, because a top title bar shrinks as you scroll past its anchor. While the page owns the scroller the bars outside it do not count, since a sticky offset there is measured against the scroller rather than the viewport. The page publishes what it is scrolling too, as --context-scroller-height, and the sidebar caps its height on that instead of on 100dvh: with the page as the scroller a viewport-tall cap hung out the bottom by exactly the height of the chrome around it.

  • A named hide-below or hide-above no longer costs you 'unsafe-inline' in your style-src. The @container rule arrived as a <style> element written into the shadow root, and a Content-Security-Policy counts that as an inline stylesheet wherever it sits. On a strict style-src 'self' the browser dropped it without a word and the cell showed at every width, so a timeline drew its mobile and its desktop variant on top of each other. The four named breakpoints are static CSS now; nothing changes in how you write them. A custom length (hide-below="320px") is the one threshold that cannot be static, so its rule is still written while the element runs. It goes into a stylesheet the shadow root adopts rather than a <style> element, and a policy judges the element rather than the CSSOM, so that path needs no 'unsafe-inline' either. No component writes a <style> element into its own shadow root any more.

  • nldd-progress-circle keeps its ring color under a strict style-src. The border on the track and on each segment is an SVG filter that floods the edge with a color, and that color went in as a literal style="flood-color: …". A policy judges an inline style attribute the same way it judges an inline stylesheet, so on style-src 'self' the browser dropped it and the filter fell back to its default, which is black. The two fixed colors are ordinary rules in the component's stylesheet now, and the one that varies per segment is set through the CSSOM, which a policy leaves alone.

v0.8.85

Compare Source

Highlights
  • box on nldd-icon, for an icon on a filled square. A feature card with a tinted tile, a menu with a colored category icon: common enough that consumers build it themselves out of a box and an icon, and then pick the two colors by eye. This turns the color question around instead. color and custom-color paint the box, and the glyph takes whatever contrasts with it, white or black, chosen on luminance by the same token the badge uses. That pair is the reason this is an option on the icon rather than a composition: it is the part nobody can check by looking. size then measures the box, so the same size renders a smaller glyph with box than without: four fifths of it, with a corner radius of a fifth. Both are ratios, so a decision about how a glyph sits in its box is one number in one place rather than a shape every consumer redraws.

  • A tulip, in two weights. tulip draws the flower as an outline: three petals, the outer two curling up beside a pointed middle one, over a short stem. tulip-light is the same flower with a thinner line, drawn on a 32 view-box like the other light weights, so its line stays 2 pixels at the size it is meant for. In this set -light names a weight rather than a state, the way circle-light reads beside circle.

Added
  • Iconstulip and tulip-light. No aliases: the name is the flower, and it says what the icon draws.
Changed
  • A line of nldd-text stops at 40em. It had no maximum at all, so in a wide column one sentence ran the full width and became a ruler. 40em is the measure the rest of the system already holds: nldd-rich-text caps its main column at 720px and a blockquote at the same, both against a body-md of 18. In ems rather than pixels, so the line holds the same number of characters at every size and a size added later is covered without anyone remembering to. New token, --semantics-text-max-width; override it, or max-width on the element, where a line is meant to run the full width, such as a label in a table cell.

  • viewfinder has the corners of viewfinder-line. Each of its four arms is a pixel longer, so a corner now reaches 6 pixels out instead of 5. The two are one pair, fit-to-view and scan, and viewfinder-line already had the longer arms. Side by side the shorter ones read as a smaller icon rather than the same frame with a line through it.

Fixed
  • A slotted icon in nldd-button gets the size the button gives its own. The button measures the icon it renders from start-icon in a span of --_icon-size, but anything you slot into start-icon or end-icon landed in a bare slot and kept whatever size it arrived with. That slot is where a mark that is not one of our icons goes, a third-party logo for instance, and every consumer had to size it by hand against a token they first had to find. The size now applies to the slotted element itself rather than to a wrapper around the slot, so a button with no icon at all still reserves no room for one.

v0.8.84

Compare Source

Highlights
  • nldd-validation-list, everything a value has to satisfy in one list. A requirement you can state up front is an item with a rule (minlength, maxlength, match or required) and it checks itself while you type. One only a server can decide is an item without a rule, and the app names it in unmet on the control. judging switches the two modes: before a verdict the list states what the field wants, after one those hints are gone for good and what is left is whatever the value fails. What turns red is invalid on the control and nothing else, so a line never goes critical under a field that looks fine. Hints are off by default: a phone number does not need its format spelled out before anyone has typed, and a password does.

  • A timeline row can leave out its dot and stay part of the track. variant="none" keeps the lane the row stands in and the status of the track running through it, so a group of rows under one step reads as one stretch instead of breaking the line. Each attribute answers one question now: size is the lane, variant what stands in it, status how far along you are. On the row that closes a track, position="only" leaves the line out altogether. See Breaking for the mapping.

  • The track lines carry the same ring as the dot, on their sides only. The dot already read over the line running under it while the line ran straight into whatever sat beside it. A spread would have banded the top and bottom too and cut the track at every row, so two offset copies of the line paint the sides and nothing else.

  • Two icons. note for what you scribble beside the work, where the file-text family is a document you file. screwdriver-wrench, aliased as tools, for the work itself rather than for a setting, which is what gear is and stays.

Breaking
  • error-message on an input is now unmet, and there is no fallback. It names the ids of the items a value does not satisfy, which is what an nldd-validation-list reads. Consumers replace the attribute wherever they set it, bound forms included: Angular's [attr.error-message] becomes [attr.unmet]. Nothing warns. nldd-form-field reads an attribute that is not there, so every message on the page silently stops appearing. One search and replace on error-message, minding that error-message-ids is a different, internal thing and keeps its name.

  • nldd-form-field-error-text is gone. An error you have to fix is a requirement the value does not meet, so it belongs in an nldd-validation-list beside the requirements that state themselves. Migration is per field and the ids travel with the texts: <nldd-form-field-error-text id="password-length">Must be at least 8 characters.</nldd-form-field-error-text> becomes <nldd-validation-item id="password-length" minlength="8">At least 8 characters</nldd-validation-item>, and anything only a server can decide becomes an item without a rule, still named in unmet. Leaving the old element in place is worse than a blank: an undefined custom element has no shadow styles, so every message renders permanently. Search for the tag; nothing warns.

  • Validation texts are the requirement, not the instruction. The same line does two jobs, a requirement up front and, once the field is judged, what is still wrong. So "Use at least 8 characters" becomes "At least 8 characters", "Pick at least one option" becomes "At least one option", and "Enter a valid company number (8 digits)" becomes "A company number of 8 digits". This touches every message a consumer has written; the examples are in skills/nldd/SKILL.md.

  • An empty nldd-list or nldd-table says nothing of its own. empty-text and empty-supporting-text are gone from both, and with them the default nldd-inline-dialog they fed. What an empty list means is the app's to write: "no assets yet" and "nothing matched that search" are different sentences with different next moves. Measured in the two applications that lean on this system hardest: 324 of their 330 lists set neither the attributes nor the slot, so all of them fell back on the same Dutch "Geen items" in an English interface, while 62 inline dialogs stood beside those lists in the consumer's own markup because the slot was never found. Put an nldd-inline-dialog in [slot="empty"], which already worked and is now the only way. Empty with that slot unfilled, both take themselves off the page rather than drawing an empty surface: on a boxed list that was a tinted bar with nothing in it, which reads as a skeleton that never loaded, and it made "nothing" look different per variant. They warn once in development instead, so a blank area becomes a question to whoever is building rather than a silence for whoever is reading. nldd-menu keeps its default: a filter that matches nothing is a state of the control, not content of the app.

  • nldd-timeline-track-cell splits variant, minor and status into size, variant and status. variant="dot|step" set the dot and the lane at once, minor sat beside it, and status="none" took the dot away from a list of values that is otherwise about progress, so a row without a dot lost the lane it had to stand in. Map variant="step" to size="md", minor to variant="minor", and status="none" to variant="none" plus the status that fits. size is sm (16px) or md (24px), and a number fits in md.

  • selected on nldd-tab-bar-item is now current. The name every other navigation component uses: nldd-menu-bar-item, nldd-breadcrumbs and, since 0.8.83, nldd-list-item. A bar that switches content still renders aria-selected and a navigation bar aria-current="page", and it was that second mode where the old name was wrong. Rename one attribute per bar. The old one does nothing, and warns in dev. nldd-document-tab-bar-item keeps selected, because those are tabs over documents rather than routes.

Added
  • A semi-bold row in the body type scale. The weight primitive was already there, 550, between medium at 475 and bold at 600, but the composed font tokens stopped at three weights. All five body sizes carry semi-bold in all four line heights now, twenty tokens, so the scale has no gap to step around.

  • --semantics-surfaces-ring-thickness, one token for the gap around something that overlaps. A badge over its anchor, an avatar over its neighbor, a timeline dot over its line, a step marker over its track: four components draw the same ring in the surrounding background so the shape in front stays readable. They took the value from two different primitives and called it two different things. The color stays in the components on purpose: it has to be the background actually behind the element, which they read from --context-parent-background-color, and a var() at token level would freeze against :root.

  • invalid is declared on every input. Seventeen of the twenty-six did not have it, while the message was already appearing: nldd-form writes the attribute and an nldd-validation-list reads it straight off the control. It worked because something else reached in for it, which is the problem error-message had. It is deliberately not drawn on a checkbox, a radio button or a switch: a red ring around one box says that option is wrong, while the question is simply unanswered. Not drawing it is no reason to stay silent, so aria-invalid goes on the element assistive software meets, the same one describedTarget() already picks.

  • nldd-form marks a field invalid the moment the platform says so. Without something joining setValidity and the attribute, a control whose validation list refused a value blocked the submit while nothing on screen said why. The browser already picks the moment, firing invalid on every failing control on submit, so the form listens in the capture phase. From that first verdict the mark follows the value both ways, and every field is judged at once rather than only the ones that failed. The native bubble goes with it: Safari put its own tooltip above the field carrying the text that already stood under it. Cancelling that event also cancels the browser's move to the first failing field, so the form makes it itself, once per round: without it a failed submit leaves focus where it was, which for anyone not looking at the screen is indistinguishable from nothing having happened. A reset takes the marks off again along with the values, because a reset fires no input and a field would otherwise keep a mark over a value the user just cleared.

  • required where it was missing. nldd-dropdown, nldd-search-field, nldd-combo-box, nldd-checkbox, nldd-switch, nldd-checkbox-field, nldd-switch-field, nldd-segmented-control and nldd-toggle-button-group did not have it. On a choice group it goes to the items, where the platform reads it: one required radio makes the whole group required and the browser writes its own message in the user's language. Radio mode only, because the same attribute on a checkbox means that box has to be ticked and HTML has no way to say "at least one of these"; aria-required goes on regardless and DEV says that nothing enforces it. nldd-number-field and nldd-stepper stay without: their value defaults to 0, so an empty one does not exist there.

  • pattern, minlength and maxlength on the text fields. No input had them, so consumers fell back on their own JavaScript for something HTML has done for fifteen years, blocking the submit included. They are on nldd-text-field, nldd-password-field, nldd-search-field and nldd-combo-box. The multi-line field gets the two lengths only, because HTML has no pattern on a textarea.

  • width="fit-content" on nldd-date-field and nldd-time-field. Both hold room at their end for a validation icon, so the field does not resize the moment it turns valid or invalid. Where a field is never validated that room is a visible gap. fit-content keeps only the air the text needs and takes the full slot back the moment a validation state does arrive, growing at the trailing edge so the date stays where it was. Opt-in, because only the consumer knows whether a field is ever validated.

  • type="form" on nldd-list, for rows you fill in rather than walk through. A row of a label and a field, a switch, a menu button, or a value you cannot change. Semantically the same as list and visually unchanged; what falls away is the keyboard of a list you walk through. No arrow navigation and no tab stop on the row: Tab goes straight to the controls, in source order. Rows that ARE actions contradict the type and warn in development. SwiftUI draws the same line between List and Form.

  • Escape, from a control back onto its row. One level up, where Shift+Tab is one step back. It works wherever you are in the row and puts the arrow keys back in reach. It claims the key at exactly that one level: on the row it claims nothing, so Escape carries on to whatever holds the list, and a control showing a menu keeps the first press for closing that menu.

  • line on nldd-timeline-track-cell, for a step that opens a group. A row that opens a group of its own is not the end of the going, so it drew an open track downward while there was progress below it. line names the halves you have covered; the other half is track still ahead, and none covers neither. It is about fill, not place: which halves are drawn stays with position, except that calling a half covered draws it.

  • An inline svg in the slot of nldd-image. Only an img and a picture were styled to fill the media box, so a drawing landed there at whatever size it brought along. It gets the same reset now, without object-fit, which does nothing on an svg because its own viewBox does the fitting. A drawing gets the box, the ratio and the caption a photo gets, and keeps its own colors.

  • no-tab on six more components. nldd-link, nldd-checkbox, nldd-radio-button, nldd-switch, nldd-toggle-button and nldd-avatar carry the property nldd-button and nldd-icon-button already had: tabindex="-1" on the control in the shadow root, so a container that runs its own focus can take it out of the tab order.

Changed
  • The title of nldd-toolbar-title and nldd-top-title-bar is semi-bold instead of medium. Both sizes of the toolbar title, and in the title bar both the plain title and the one step smaller title that carries a subtitle. The subtitles stay regular.
Fixed
  • required, pattern, minlength and maxlength now stop a submit. They are handed to a control inside the shadow root, and that control is not a member of the form around the component: the form sees the host, the host sees the control, and nothing joined the two. The form-associated mixin reports the control's verdict as the host's own now, which fixes every input that renders one. A group (nldd-segmented-control, nldd-toggle-button-group) and the editors have no such control and are unchanged. Two consequences: a form that used to go through with an empty required field now stops, and outside an nldd-form the browser shows its own bubble, the way it does for any form control.

  • A validation list and a native constraint no longer erase each other. setValidity writes the whole set of flags at once, so whoever wrote last won: a list reporting its failing rule cleared the field's required, and the next render cleared the rule. Both go through setCustomValidity now, which the mixin keeps beside the native flags rather than in place of them. A plain native input answers to the same method, so a list outside this system's inputs still works.

  • A validation list keeps listening after its field is moved. Taking the element out of the DOM and putting it back, which is what a consumer portalling a sheet to document.body does, takes the input listener with it. Reconnecting found the same control as before and concluded there was nothing to wire up, leaving the list reacting to nothing. It now also checks whether it still has an observer.

  • An empty name or width no longer reaches the DOM. Every input carried name="" and half of them width="", on every instance in every consumer app. Twenty-two components reflect them only when they hold something now.

  • The input of nldd-number-field keeps its minimum width. Three rules hung on :host([width]), and that selector always matched, because an empty width reflected to the DOM. Underneath sat a real bug: the base rule gives the input a min-width and the always-matching rule overwrote it with 0, taking hide-spin-buttons down with it, which sets 80 pixels and got 0.

  • The description of a field reaches the screen reader. It arrived at 3 of the 26 inputs, measured against the accessibility tree: the field wrote aria-describedby on the input in its own shadow root while the ids it named stood in the document, and an IDREF is looked up in the tree of the element that carries it. The elements themselves go through now, via ariaDescribedByElements, and a mixin hands the question down through a wrapper to the component that knows which element inside itself is the control.

  • The website title in nldd-top-navigation-bar is a 24 by 24 target. WCAG 2.5.8 asks that of anything you click, and this one was 20 high on a phone and 23 at medium. The row around it was 24, but that came from padding on the bar, which does nothing for the target inside it. The title carries its own minimum now, in both directions and whether or not it links anywhere.

  • Avatars in a group overlap by a fifth of their diameter, whatever that diameter is. The overlap was a flat 8 pixels: 29% of the avatar at size 28 and 8% at size 96, so the same component read as a tight stack at one size and a row of separate circles at another. A fifth is what 8 was at the default size of 40, so a group at that size looks exactly as it did.

  • A form section stands 24 pixels from its neighbors on both sides. It carried padding, and padding does not collapse, so it added itself to the 16 the form already puts under the preceding child. It is a margin now: 16 and 24 beside each other are 24. On both sides rather than only above, because with a top margin alone the distance under a section depended on what happened to follow it.

  • The buttons of a form stand 24 pixels below it. Also in a form without sections, where the actions used to be one more gap like everything else. The end of a form is the end, not one more question.

  • A section heading sits 8 pixels above its fields instead of 16. It belongs to what is under it, and the same distance on both sides left it floating between the group above and the group below.

  • A form section spaces whatever you put in it. Its children leaned on a rule that only recognized one field followed by another, so a button or a paragraph inside a section got no room and every caller added it themselves. The section uses the same rhythm as the form around it now, and asks whether something visible follows rather than what the thing is.

  • A field label sits four pixels above its control instead of three, in the flat type rather than the tight one. Three was close enough to read as attached and far enough to look like a mistake, and the tight leading pulled a wrapping label onto itself.

  • One gap between everything in a form. Two fields in a row used to sit ten pixels apart instead of sixteen, and it took two container queries and four :not()s to say so. It bought six pixels per pair, while a toggle group or a combo box stood ten pixels from the next label. A field, a section, a button, a paragraph: whatever follows another thing stands the same distance from it now, and a section or the buttons of a form stand a step further off. The three gap tokens are two, named after what they separate: --semantics-forms-fields-gap and --semantics-forms-sections-gap. --semantics-forms-gap and --semantics-forms-gap-tight are gone; nothing outside this package used either.

  • nldd-form-section no longer draws lines around itself. A section carries a heading, and a rule above and below says the same thing a second time. What gave it away was the suppressing: no line on the first child, none between two adjacent sections, none on the last, and that last exception did not work, so every form with a submit button had a rule drawn right above that button. The padding stays and is what groups.

  • A close from an overlay stays with the overlay it came from. nldd-sheet, nldd-window, nldd-modal-dialog and nldd-popover fired close with bubbles: true, so an overlay inside another one reached the listener on the one around it. A consumer binding (close) on the outer overlay got the inner one's close as well and tore down what was still open. The event no longer bubbles. Anyone checking event.target to work around this can stop.

  • Escape closes what is on top, and nothing behind it. A combo box, a token field and anything on nldd-popover closed on Escape and let the same press travel on, so a sheet or window behind them went too. They stop the press the way nldd-menu already did. With nothing open they claim nothing, so Escape still reaches the sheet.

  • A list no longer takes the arrow keys out of a control in one of its rows. The keys were claimed by whichever list they bubbled through. A combo box in a row lost its menu on ArrowDown, and a text field lost its caret on Home. The list stands down now while the key comes from a control the row holds; what the row IS keeps answering, since a link, button or segment is not a control it holds.

  • A list is one tab stop from the outside again. The current row hands its controls a place in the tab order, and those places stayed handed out after focus left the list, so tabbing back in dropped you into the control you had left rather than on the row. Leaving the list parks them again.

  • The clear and picker buttons of a combo box sit in the middle again. Both are wrapped in a plain block, so the wrapper took the height of the line box it inherited and the button hung at the top of it: centered in a story, high in an app whose body sets a taller line-height. They are flex now, like the file, time and date fields.

  • A row let go outside itself no longer stays lit. nldd-list-item and nldd-list-item-segment showed the press on pointerdown and cleared it on a pointerup on the row, which is not where the event lands when you release off the row. The release is watched on the window now, for as long as a press is running.

  • nldd-box and nldd-card take the width they are given. An nldd-cell lines its content up on the left rather than stretching it, so a box or a card inside one shrank to its content: in a narrow cell that came down to a single letter with the title running vertically. As a block that already happened, as a flex item it did not.

  • The scroll mode follows a split view that arrives late. A consumer that renders its split view behind a condition, such as a loading state or an empty state, takes it away and puts it back. While it was gone the derivation said root, and the split view that comes back measures itself as what it already was, so it announces nothing: every pane kept scrolling as one document until a reload. A layer arriving or leaving now re-runs the derivation.

  • A bar in a sheet keeps its back button. A split view hides the back button of a bar inside a pane, because the menu beside you is the way back. That hiding traveled down the whole pane, so a sheet standing in one lost the back button of its own bar. A sheet starts that context over.

  • The warning about a control a row cannot reach now fires, and names the control. It ran while the list rendered, and a slotted custom element has its shadow root by then but has not rendered into it, so the check found nothing. It waits for the control to have rendered, runs again for rows added later, and reports once. It stays quiet where nothing is wrong: a segment, a disabled or negatively-tabindexed control, and anything the component hides itself.

  • Lit no longer reports an update scheduled after an update completed. nldd-list, nldd-list-item, nldd-collection, nldd-menu, nldd-menu-item and nldd-navigation-split-view set state from firstUpdated, from the first slotchange or from updated, which is a dev-mode warning on every page holding a list or a menu. What the first render needs is read from the light DOM before it now, and what can only be read afterwards is set out of the update cycle.

  • A branch row that starts collapsed no longer warns. The row reads an absent expanded as collapsed and emits aria-expanded="false", so the warning asked for something that was already there. The warning about slot="children" outside a type="tree" list stays, and reports once per row instead of on every change.

v0.8.83

Compare Source

Highlights
  • nldd-text, a component for body text. New, and the one piece of typography that had no component: every line that is not a heading and not a block of prose. It offers size (xxs through lg), weight, line-height, color and horizontal-alignment, all of them names for what the token scale already holds, so it cannot invent typography. Color follows the content channel a row, menu or table sets, so a line travels with the row it sits in. Reach for nldd-title for a heading, nldd-rich-text for prose with its own rhythm.

  • Every list answers to the arrow keys, and a row may hold more than one control. One list now behaves like the next: ArrowUp and ArrowDown walk the rows there is something to do with, Home and End jump to the ends, and the list as a whole is one tab stop. Within the current row Tab walks its segments and the controls in its cells, while those same controls in the other rows stay out of the tab order. Two types mean something else by the same keys, and you can see which before you touch one: a listbox runs its keyboard from its search field, and a reorderable list moves rows.

  • current beside selected in a list row. current is the row you are on, selected a row you picked, and a list can show both. At rest they look the same, so existing lists do not change. The difference shows when focus is in the row: only the current row takes the accent fill, and it does so from a nested nldd-list-item-segment too. Row states now sit on the same ladder as button states, with a rest, hover and press step for each, under the names the buttons already use. Seven tokens more.

  • A color the system does not know, on a badge and an icon. Some colors are not a category but a fact: the jacket of a cable is blue whatever the house style does. custom-color on nldd-badge and nldd-icon takes any CSS color value and wins over color, so the vocabulary of color stays closed and one search shows where an app steps outside the palette. On the badge, what sits on the color turns white or black, whichever contrasts better.

  • Thirty icons added, three redrawn. Added: cpu, gpu, memory-chip, psu, pci-card, transceiver-module, rack-server, rack-servers, external-hard-drives, ssd-hard-drive, external-hard-drive, network-switch, kvm-switch, power-plug, lightning, snowflake, boxes-3, clipboard-bullet-list, kanban-columns, printer, shield-arrow-right-arrow-left, waving-crossing-lines, circle, circle-circle, minus-circle, circle-light, circle-circle-light, check-mark-circle-light, clock-light and slash-circle-light. Redrawn: shield, shield-check-mark and shield-lock.

Breaking
  • database-disabled and database-unavailable are now disabled-database and unavailable-database. The set puts the state in front everywhere else: new-namespace, no-priority, checked-small. Those two trailed it, and were the only aliases that did apart from the -light weights, which name the drawing rather than a state. Both still point at cylinder-split-slash. Consumers on an old name change one string.

  • nldd-list-item-action is now nldd-list-item-segment. The component was not always an action: without href, button or checkbox, and inside a listbox, it renders as a plain container. What it always is, is a piece of the row with its own hit area and its own fill, which is what the docs had been calling it all along. Consumers rename the element and the import path, and the attributes stay the same. The host classes follow: has-leading-segment and has-trailing-segment.

  • Every list answers to the arrow keys, and arrow-navigation is gone. Whether the arrow keys work is what a reader may assume about every list, not a setting per list, and as a setting it was unknowable from the outside. Consumers drop the attribute. Leaving it in does nothing, and warns in dev.

  • nldd-box paints with background, like every other surface. The attribute was variant, which in this system is the word for what a component is, not for which surface it draws. nldd-app-view, nldd-page, nldd-split-view-pane and nldd-card all call that background. So variant="base|tinted|critical" is now background="base|tinted|critical". One thing to know: a box starts on tinted where a card starts on base, because a box stands out from the page and a card sits on it. Consumers change one string per box.

  • The plus badges are named after what they make. add-namespace is now new-namespace, add-k8s and add-kubernetes are new-k8s and new-kubernetes, and add-text-document is gone in favor of new-text-document. A namespace, a cluster and a document are made, not added. add stays for putting something that exists into something else, which is why add-plugin, add-user, add-location and add-emoji are unchanged. Consumers on an old name change one string.

Added
  • Eighteen icons for a data center: the hardware and the network. cpu, gpu, memory-chip, psu, pci-card, transceiver-module, rack-server, rack-servers, external-hard-drives, ssd-hard-drive, external-hard-drive, network-switch, kvm-switch, power-plug, lightning, snowflake, waving-crossing-lines (network-patch-mapping) and shield-arrow-right-arrow-left (firewall), with the aliases server, servers, memory, ram, processor, power, cooling, airco, hard-drive, nic, network-interface-card, sfp and energy. Two are named after what they draw rather than what they came in for: network-switch, because switch on its own is the verb, and snowflake. arrow-left-right gets that verb as an alias: switch and swap, for moving between two things. There are two racks: rack-servers drawn flat on for a diagram, and rack-server at an angle for a row, where three stacked bays turn into three stripes. server points at the angled one, servers at the flat one. waving-crossing-lines draws the runs themselves, sweeping from one side to the other and crossing on the way, rather than the nodes they connect, which is what a patch panel looks like from the front. Two of them are a pair: external-hard-drive is one unit, external-hard-drives the stack of three, aliased storage. The firewall shield carries the traffic it filters, two arrows passing each other, so it says what it does instead of only saying "safe".

  • Eight icons for getting things done. The three task states read as one series: circle for to do and circle-circle for doing, beside the check-mark-circle the set already had, aliased as to-do, doing and done. minus-circle is in the set as well: the same ring with a bar across it. kanban-columns draws a board of work in columns (kanban), and circle-grid-2x2-top-left-check-mark gets the alias all-tasks. circle doubles as no-priority: the same ring as low-priority through high-priority with nothing in it, so that column keeps reading as a count from zero up. Four of the state icons come in a light weight drawn for 32 pixels: circle-light, circle-circle-light, check-mark-circle-light and clock-light keep the 2-pixel line on a 32 view-box instead of a 24 one, so the line stays 2 pixels at the size they are drawn for rather than growing to 2.7. Use them where a state icon leads a row at 32, and reach for the regular set below that. Aliased as to-do-light, doing-light and done-light.

  • Four icons outside those two groups, and one alias. boxes-3 for a stock of things you own (inventory), clipboard-bullet-list for that same list read as a checklist (inventory-alt), printer for printing (print), and slash-circle-light in the same light weight as the state icons (blocked-light). brackets-ellipsis gets the alias namespace, the meaning the brackets with the ellipsis carry wherever the console uses them.

  • color="inherit" on nldd-badge. Fill with the content color around it (--context-content-color, otherwise currentColor), the meaning it already has on nldd-avatar, so a badge travels with the line it sits in.

  • disabled works on a row and on a link. A segment could be switched off and a row could not, while a row can just as well be the button or the checkbox. A button or checkbox row takes the real off state and dims. A link gets aria-disabled and a blocked click, because an anchor cannot be switched off the way a button can, and that goes for a link segment as well: there disabled used to do nothing at all. Hover and press do nothing there, and the arrow keys skip it: a disabled control is not focusable, so a stop there would swallow the key. That already applied to a disabled segment, so a row whose only segment is off is no longer a stop either.

  • A current segment makes its whole row current. A row built from segments has no link of its own, so aria-current="page" belongs on the segment that holds the link. The row reads it off its own segments and paints itself, so it is written once. Reading, not writing: the attributes stay the consumer's. current also appears in the controls of nldd-list-item and nldd-list-item-segment now.

  • A ticked row is painted, the same as a ticked segment. A row that is the checkbox itself took no fill, so the same list looked different depending on where the tick sat. A ticked row is a row you picked, so it paints what selected paints, with the same hover and press steps. Only together with checkbox.

  • A segment stays lit while what it opened is on screen. expanded on a segment set aria-expanded and turned a chevron, but left the segment looking untouched, so a menu hanging off one row floated over the list without saying which row it belonged to. It now paints the same fill as a picked row (--components-list-item-is-expanded-background-color, two steps above hover), and after the hover rule, so the pointer cannot dim it while the menu is open.

  • no-tab on nldd-button. The same property nldd-icon-button already had: take the button out of the tab order (tabindex="-1" on the button in the shadow root) because a container around it manages focus itself. An nldd-list sets it on the buttons in the rows that are not the current one. Mouse and script still reach the button.

  • Enter follows the row in a tree. It activates the row's own link or button, or on a branch built from segments the first one that is not the chevron. Space folds, the same as Left and Right. Enter used to fold, so a branch you could reach with the arrow keys could not be opened without hunting for a segment with Tab.

  • readonly on nldd-combo-box. The value stays readable and submits with the form, and the control that would change it is gone rather than dimmed.

  • keyboard and enter-key on the text fields. They set inputmode and enterkeyhint, so a phone shows the right keys and the right label on the return key.

Changed
  • inbox is now tray, and a set of aliases moved with the way the set names things. The file says what it draws, a tray with a slot, and inbox stays as the alias for what it is used for. New aliases: time-light on clock-light, locked-database on cylinder-split-badge-lock, checked-text-document on file-text-badge-check-mark, companies on apartment-building-2, add-extension and add-module on puzzle-piece-badge-plus (the plain piece already answered to all three words, the plus badge only to plugin), and the directory vocabulary is carried through the folders it was missing from: new-directory, folders, directories, open-folder and open-directory.

  • The three shields are redrawn on one silhouette. shield, shield-check-mark and shield-lock shared a pointed crest built from arcs, and now stand on the same flat-topped, rolled-edge shield as shield-arrow-right-arrow-left. That shield is wider at the top and flatter, and that is the reason for it: more fits on it. A check mark, a lock or two arrows passing each other need room, and on a pointed crest such a figure runs into the slanted edges. Four shields that differ only in what they carry, rather than a family that splits the moment a fourth arrives.

  • A read-only field takes its controls away instead of dimming them. A dimmed control invites a click that does nothing. The field keeps its value and its label, and drops the spinner, the picker and the clear button.

  • A subtitle in nldd-title is one step larger. It sat two steps under the title, which read as a caption rather than as the line that belongs to it.

Fixed
  • The text on a colored fill is picked on luminance now, and clears 4.5:1. --semantics-content-contrast-color decided between black and white on OKLCH lightness, which is perceptual and parts ways with the luminance WCAG measures, so a saturated mid blue got white text at 3.68:1. The threshold is now the relative luminance where black and white give the same contrast, Y = (sqrt(21) - 1) / 20, which makes the picked color at least 4.58:1 whatever it lands on. Where the browser has contrast-color(), that answers instead. This reaches every component that puts content on a fill it was handed: nldd-badge, nldd-avatar, nldd-step-indicator, nldd-keyboard-shortcut and the timeline cell.

  • Switching a row off now moves the tab stop with it. A row that carried the list's single tab stop kept it after disabled landed on it, so Tab reached a row that answers to nothing. The arrow keys already skipped it, and an unrelated change happened to repair it, which is the worst kind of bug: it fixes itself while you look at it. The row re-runs the roving when its own disabled changes, and the list watches the attribute too.

  • A read-only combo box no longer announces a list it cannot open. The picker button and the menu were already gone, but the input kept role="combobox" with aria-haspopup="listbox", aria-autocomplete and aria-expanded. It is a text field with a value in it, and it now says so.

  • color="inherit" on a badge or an avatar could paint white on white. The fill came from the content channel around it, while the contrast flip that decides between black and white text read the inherited color. Those are the same value in most places, so it went unnoticed until a row set the channel to white over dark text: the badge then took a white fill and, from the dark inherited color, white text as well. Both components now set their own color to the fill first, the way custom-color already did, so the flip is computed against what is actually painted.

  • An overlay is where the app's layout context ends. A sheet or dialog inherited the app's scroll mode, so a short page inside an overlay could float its sticky footer mid-screen. An overlay now starts its own context.


Configuration

📅 Schedule: (in timezone Europe/Amsterdam)

  • Branch creation
    • "before 6am on sunday"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Mend Renovate CLI.

This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [@nldd/design-system](https://github.com/MinBZK/storybook) | [`^0.8.82` → `^0.8.86`](https://renovatebot.com/diffs/npm/@nldd%2fdesign-system/0.8.82/0.8.86) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@nldd%2fdesign-system/0.8.86?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nldd%2fdesign-system/0.8.82/0.8.86?slim=true) | --- ### Release Notes <details> <summary>MinBZK/storybook (@&#8203;nldd/design-system)</summary> ### [`v0.8.86`](https://github.com/MinBZK/storybook/blob/HEAD/CHANGELOG.md#0886-2026-09-03) [Compare Source](https://github.com/MinBZK/storybook/compare/v0.8.85...v0.8.86) ##### Highlights - **No `'unsafe-inline'` in your `style-src` for our components.** Two of them wrote styling into the page itself, which is exactly what a Content-Security-Policy stops: under a strict policy a cell hid at no width at all, and a progress circle lost the color of its ring, both without a word. The rules travel through the CSSOM now, which a policy leaves alone. What each one did is in Fixed below. - **A favicon, as `@nldd/design-system/favicon.svg`.** The package shipped the logo, the fonts, the reset and the tokens, and left the tab strip to every site to redraw. They drifted, down to two shades of the same blue. This is the arms filling the tile on the lintje `#154273`, the pattern logius.nl and MijnOverheid Zakelijk already use, and the one that survives 16 pixels where the ribbon turns to mush. Copy it into whatever you serve statically and point a `<link rel="icon">` at it. A browser loads a favicon apart from the page, so your CSS cannot reach it: another background is a copy of the file with one `fill` changed, which is what the sites that put their own house color around it are doing anyway. There is a PNG of the same tile beside it, `@nldd/design-system/touch-icon.png`, because Safari will not take an SVG for the icon on the home screen. It is 180 square, the size Apple asks for, and it is one file rather than a ladder of sizes. This Storybook now wears both. - **On a narrow screen the wordmark beside the ribbon is centred against it.** It started at the ribbon's midpoint and grew down from there, so a one-line wordmark hung against the bottom half of the mark with the space above it empty. It is centred now, and only what does not fit grows downward: centring a taller wordmark would push its first line off the top of the page, which is where the ribbon starts. Once it does grow it keeps 12px from that edge, and nothing under the last line, so the bar is no taller than the text in it. An auto margin does the work, so there is no height to guess and no breakpoint to keep in step with the text. Above sm nothing changes. ##### Breaking - **A CSS length in `gap` on `nldd-collection` no longer applies.** It was the only component that took one. Use the step with the same number: `gap="16px"` becomes `gap="16"`. A gap that has to change with the viewport is `sm-gap` / `md-gap` / `lg-gap` rather than a length. - **`--context-layer-top` and `--context-layer-bottom` are now `--context-inset-top` and `--context-inset-bottom`.** The value says how much of the edge is taken by chrome that a sticky element has to clear, and layer reads as stacking order, which is what `z-index` and `@layer` are for. Inset is the word the platform already uses for this, as in `env(safe-area-inset-top)` and Android's window insets, and the same number is what `scroll-padding-top` wants, which has nothing sticky about it. Rename it wherever your app publishes or reads it. A CSS variable that no longer exists fails without a word, so nothing warns: sticky content lands against the top edge instead of below your bar. `nldd-app-view`, `nldd-page`, `nldd-sheet`, `nldd-bar-split-view` and `nldd-modal-dialog` all speak the new name. ##### Added - **`gap` is a step of the spacing scale in both components, and so is `padding` in `nldd-container`.** `nldd-collection` read `gap` as a CSS length and `nldd-container` read it as a scale step, so `gap="16"` was right in one and wrote `--_gap: 16` in the other, which is not a length: the declaration fell away and the space between the items became nothing at all, without a word. One meaning now, behind one resolver. Spacing is rhythm, so a value beside the scale is not a finer choice but a break in the pattern, and a length is refused like any other value that cannot be placed: nothing is written, the default stands, and dev names the value rather than leaving you to spot the collapse. `nldd-collection` gains `sm-gap`, `md-gap` and `lg-gap` on the way, which `nldd-container` already had and which is where a gap that has to vary belongs. - **`layout="lanes"` on `nldd-collection`.** Items of unequal height packed into columns, the masonry `nldd-container` has had for a while, now on the component that pages. `item-width` is the minimum column width, the same as it is in `grid`. Where the browser has `grid-lanes` it uses it; where it does not it falls back to that grid rather than to the multicol `nldd-container` falls back to. That is the whole reason the two differ: multicol fills column by column and redistributes every item each time `show-load-more` adds to the set, so what you had already read moves. Grid fills row by row, like native lanes, and grows at the bottom. What you give up in the fallback is the ragged edge, which is a look rather than a behaviour. - **A row can be a radio: `radio` on `nldd-list-item`, `type="radiogroup"` on `nldd-list`.** A choice out of a handful used to mean an `nldd-radio-button-group`, which gives every option a label and nothing else. As rows the options can carry what a filter list wants beside them, a count that lines up on the right and reads as secondary, and the whole row is the target instead of the dot. The row is the control, the way `checkbox` already was: the action becomes a `role="radio"` button with `aria-checked`, activation sets `checked` and never clears it, and `change` fires once, the way a native radio stays put when you pick what was already picked. Slot an `nldd-radio-button decorative` for the shape. The group is the parent: `type="radiogroup"` makes the items region a `role="radiogroup"` and the rows step out of the tree with `role="none"`, so the radios are the group's own children rather than list items wrapped around them. ##### Changed - **A sticky fade is 24px, and a sidebar keeps 24px from the edges.** The fade under `nldd-page`'s sticky header and above its sticky footer was 32px, and the default sticky inset of `nldd-sidebar-section` was 16px, so a box that cleared the header still began inside its fade. Both are 24px now, which is what lets the sidebar start where the fade ends. Nothing to set for it, and `sticky-top` / `sticky-bottom` still override. ##### Fixed - **An empty list and a list that found nothing are two states now, and `[slot="no-results"]` is the second one.** They used to be one: `[slot="empty"]` said the same sentence to someone who has no assets yet and to someone whose search matched none of their twenty. The list can tell them apart, because it knows both how many rows it has and how many are `[hidden]`, so it now picks the one that fits and falls back to `empty` when `no-results` is not given. What changes with it is the chrome. Rows that are only filtered away keep the search field and the `[slot="toolbar"]`, because those are the way back to the rows: since 0.8.84 a query that matched nothing took the whole list off the page, search field and filters included, leaving no way back at all. No rows at all hides them instead, since there is nothing to search or filter, and with nothing in `[slot="empty"]` the list still leaves the page entirely. A list that fetches its rows is in that state until they arrive: put an `nldd-inline-dialog variant="loading"` in `empty` to hold the place, rather than have the controls appear a moment later. And a slot with nothing in it draws no box any more, where an empty surface read as a skeleton that never loaded. - **A long word in the wordmark no longer shoves the ribbon off centre.** A grid item is at least as wide as its longest word unless it is told otherwise, and the wordmark sits in one of the two tracks that hold the ribbon in the middle. One unbreakable name pushed the ribbon 137px to the left and the page 43px past the screen, so a phone got a horizontal scrollbar on the bar alone. The name wraps mid-word now, with `overflow-wrap: anywhere` rather than `break-word`, because only that one counts the break when the track is measured. Wrapped and not truncated: a name cut off by an ellipsis cannot be read at all. - **The wordmark measures itself against the ribbon of the breakpoint it is in.** `--_logo-width` was declared per breakpoint inside the logo element itself, so everything outside it kept reading the small value: on lg the wordmark reserved 80px beside a 96px ribbon and the spacer that places it was 8px short. The declaration sits on the bar now, where the ribbon and the wordmark both read it. - **Sticky content inside a page clears the page's own bars.** `nldd-page` said nothing about its sticky header, so an `nldd-sidebar-section` in one stuck to the top of the viewport and slid under it: the top of the sidebar sat behind the bar, and the height cap was a whole viewport where a viewport minus the bars was left, which ran the bottom of the box off the screen. The page now adds its sticky header and footer to `--context-inset-top` and `--context-inset-bottom` for its content, the same variables the shell already uses to tell the page where it may stick. `nldd-sidebar-section` reads them, so its box lands 24px below whatever is above it and ends 24px above whatever is below, the depth of the fade a sticky header casts, so it starts where that fade ends, and `sticky-top` / `sticky-bottom` go back to being for chrome the page cannot know about. The heights are measured rather than assumed, because a top title bar shrinks as you scroll past its anchor. While the page owns the scroller the bars outside it do not count, since a sticky offset there is measured against the scroller rather than the viewport. The page publishes what it is scrolling too, as `--context-scroller-height`, and the sidebar caps its height on that instead of on `100dvh`: with the page as the scroller a viewport-tall cap hung out the bottom by exactly the height of the chrome around it. - **A named `hide-below` or `hide-above` no longer costs you `'unsafe-inline'` in your `style-src`.** The `@container` rule arrived as a `<style>` element written into the shadow root, and a Content-Security-Policy counts that as an inline stylesheet wherever it sits. On a strict `style-src 'self'` the browser dropped it without a word and the cell showed at every width, so a timeline drew its mobile and its desktop variant on top of each other. The four named breakpoints are static CSS now; nothing changes in how you write them. A custom length (`hide-below="320px"`) is the one threshold that cannot be static, so its rule is still written while the element runs. It goes into a stylesheet the shadow root adopts rather than a `<style>` element, and a policy judges the element rather than the CSSOM, so that path needs no `'unsafe-inline'` either. No component writes a `<style>` element into its own shadow root any more. - **`nldd-progress-circle` keeps its ring color under a strict `style-src`.** The border on the track and on each segment is an SVG filter that floods the edge with a color, and that color went in as a literal `style="flood-color: …"`. A policy judges an inline style attribute the same way it judges an inline stylesheet, so on `style-src 'self'` the browser dropped it and the filter fell back to its default, which is black. The two fixed colors are ordinary rules in the component's stylesheet now, and the one that varies per segment is set through the CSSOM, which a policy leaves alone. ### [`v0.8.85`](https://github.com/MinBZK/storybook/blob/HEAD/CHANGELOG.md#0885-2026-08-31) [Compare Source](https://github.com/MinBZK/storybook/compare/v0.8.84...v0.8.85) ##### Highlights - **`box` on `nldd-icon`, for an icon on a filled square.** A feature card with a tinted tile, a menu with a colored category icon: common enough that consumers build it themselves out of a box and an icon, and then pick the two colors by eye. This turns the color question around instead. `color` and `custom-color` paint the box, and the glyph takes whatever contrasts with it, white or black, chosen on luminance by the same token the badge uses. That pair is the reason this is an option on the icon rather than a composition: it is the part nobody can check by looking. `size` then measures the box, so the same size renders a smaller glyph with `box` than without: four fifths of it, with a corner radius of a fifth. Both are ratios, so a decision about how a glyph sits in its box is one number in one place rather than a shape every consumer redraws. - **A tulip, in two weights.** `tulip` draws the flower as an outline: three petals, the outer two curling up beside a pointed middle one, over a short stem. `tulip-light` is the same flower with a thinner line, drawn on a 32 view-box like the other light weights, so its line stays 2 pixels at the size it is meant for. In this set `-light` names a weight rather than a state, the way `circle-light` reads beside `circle`. ##### Added - **Icons** — `tulip` and `tulip-light`. No aliases: the name is the flower, and it says what the icon draws. ##### Changed - **A line of `nldd-text` stops at 40em.** It had no maximum at all, so in a wide column one sentence ran the full width and became a ruler. 40em is the measure the rest of the system already holds: `nldd-rich-text` caps its main column at 720px and a blockquote at the same, both against a body-md of 18. In ems rather than pixels, so the line holds the same number of characters at every size and a size added later is covered without anyone remembering to. New token, `--semantics-text-max-width`; override it, or `max-width` on the element, where a line is meant to run the full width, such as a label in a table cell. - **`viewfinder` has the corners of `viewfinder-line`.** Each of its four arms is a pixel longer, so a corner now reaches 6 pixels out instead of 5. The two are one pair, `fit-to-view` and `scan`, and `viewfinder-line` already had the longer arms. Side by side the shorter ones read as a smaller icon rather than the same frame with a line through it. ##### Fixed - **A slotted icon in `nldd-button` gets the size the button gives its own.** The button measures the icon it renders from `start-icon` in a span of `--_icon-size`, but anything you slot into `start-icon` or `end-icon` landed in a bare slot and kept whatever size it arrived with. That slot is where a mark that is not one of our icons goes, a third-party logo for instance, and every consumer had to size it by hand against a token they first had to find. The size now applies to the slotted element itself rather than to a wrapper around the slot, so a button with no icon at all still reserves no room for one. ### [`v0.8.84`](https://github.com/MinBZK/storybook/blob/HEAD/CHANGELOG.md#0884-2026-08-30) [Compare Source](https://github.com/MinBZK/storybook/compare/v0.8.83...v0.8.84) ##### Highlights - **`nldd-validation-list`, everything a value has to satisfy in one list.** A requirement you can state up front is an item with a rule (`minlength`, `maxlength`, `match` or `required`) and it checks itself while you type. One only a server can decide is an item without a rule, and the app names it in `unmet` on the control. `judging` switches the two modes: before a verdict the list states what the field wants, after one those hints are gone for good and what is left is whatever the value fails. What turns red is `invalid` on the control and nothing else, so a line never goes critical under a field that looks fine. Hints are off by default: a phone number does not need its format spelled out before anyone has typed, and a password does. - **A timeline row can leave out its dot and stay part of the track.** `variant="none"` keeps the lane the row stands in and the status of the track running through it, so a group of rows under one step reads as one stretch instead of breaking the line. Each attribute answers one question now: `size` is the lane, `variant` what stands in it, `status` how far along you are. On the row that closes a track, `position="only"` leaves the line out altogether. See Breaking for the mapping. - **The track lines carry the same ring as the dot, on their sides only.** The dot already read over the line running under it while the line ran straight into whatever sat beside it. A spread would have banded the top and bottom too and cut the track at every row, so two offset copies of the line paint the sides and nothing else. - **Two icons.** `note` for what you scribble beside the work, where the `file-text` family is a document you file. `screwdriver-wrench`, aliased as `tools`, for the work itself rather than for a setting, which is what `gear` is and stays. ##### Breaking - **`error-message` on an input is now `unmet`, and there is no fallback.** It names the ids of the items a value does not satisfy, which is what an `nldd-validation-list` reads. Consumers replace the attribute wherever they set it, bound forms included: Angular's `[attr.error-message]` becomes `[attr.unmet]`. Nothing warns. `nldd-form-field` reads an attribute that is not there, so every message on the page silently stops appearing. One search and replace on `error-message`, minding that `error-message-ids` is a different, internal thing and keeps its name. - **`nldd-form-field-error-text` is gone.** An error you have to fix is a requirement the value does not meet, so it belongs in an `nldd-validation-list` beside the requirements that state themselves. Migration is per field and the ids travel with the texts: `<nldd-form-field-error-text id="password-length">Must be at least 8 characters.</nldd-form-field-error-text>` becomes `<nldd-validation-item id="password-length" minlength="8">At least 8 characters</nldd-validation-item>`, and anything only a server can decide becomes an item without a rule, still named in `unmet`. Leaving the old element in place is worse than a blank: an undefined custom element has no shadow styles, so every message renders permanently. Search for the tag; nothing warns. - **Validation texts are the requirement, not the instruction.** The same line does two jobs, a requirement up front and, once the field is judged, what is still wrong. So "Use at least 8 characters" becomes "At least 8 characters", "Pick at least one option" becomes "At least one option", and "Enter a valid company number (8 digits)" becomes "A company number of 8 digits". This touches every message a consumer has written; the examples are in `skills/nldd/SKILL.md`. - **An empty `nldd-list` or `nldd-table` says nothing of its own.** `empty-text` and `empty-supporting-text` are gone from both, and with them the default `nldd-inline-dialog` they fed. What an empty list means is the app's to write: "no assets yet" and "nothing matched that search" are different sentences with different next moves. Measured in the two applications that lean on this system hardest: 324 of their 330 lists set neither the attributes nor the slot, so all of them fell back on the same Dutch "Geen items" in an English interface, while 62 inline dialogs stood beside those lists in the consumer's own markup because the slot was never found. Put an `nldd-inline-dialog` in `[slot="empty"]`, which already worked and is now the only way. Empty with that slot unfilled, both take themselves off the page rather than drawing an empty surface: on a boxed list that was a tinted bar with nothing in it, which reads as a skeleton that never loaded, and it made "nothing" look different per variant. They warn once in development instead, so a blank area becomes a question to whoever is building rather than a silence for whoever is reading. `nldd-menu` keeps its default: a filter that matches nothing is a state of the control, not content of the app. - **`nldd-timeline-track-cell` splits `variant`, `minor` and `status` into `size`, `variant` and `status`.** `variant="dot|step"` set the dot and the lane at once, `minor` sat beside it, and `status="none"` took the dot away from a list of values that is otherwise about progress, so a row without a dot lost the lane it had to stand in. Map `variant="step"` to `size="md"`, `minor` to `variant="minor"`, and `status="none"` to `variant="none"` plus the status that fits. `size` is `sm` (16px) or `md` (24px), and a number fits in `md`. - **`selected` on `nldd-tab-bar-item` is now `current`.** The name every other navigation component uses: `nldd-menu-bar-item`, `nldd-breadcrumbs` and, since 0.8.83, `nldd-list-item`. A bar that switches content still renders `aria-selected` and a `navigation` bar `aria-current="page"`, and it was that second mode where the old name was wrong. Rename one attribute per bar. The old one does nothing, and warns in dev. `nldd-document-tab-bar-item` keeps `selected`, because those are tabs over documents rather than routes. ##### Added - **A semi-bold row in the body type scale.** The weight primitive was already there, 550, between medium at 475 and bold at 600, but the composed `font` tokens stopped at three weights. All five body sizes carry `semi-bold` in all four line heights now, twenty tokens, so the scale has no gap to step around. - **`--semantics-surfaces-ring-thickness`, one token for the gap around something that overlaps.** A badge over its anchor, an avatar over its neighbor, a timeline dot over its line, a step marker over its track: four components draw the same ring in the surrounding background so the shape in front stays readable. They took the value from two different primitives and called it two different things. The color stays in the components on purpose: it has to be the background actually behind the element, which they read from `--context-parent-background-color`, and a `var()` at token level would freeze against `:root`. - **`invalid` is declared on every input.** Seventeen of the twenty-six did not have it, while the message was already appearing: `nldd-form` writes the attribute and an `nldd-validation-list` reads it straight off the control. It worked because something else reached in for it, which is the problem `error-message` had. It is deliberately not drawn on a checkbox, a radio button or a switch: a red ring around one box says that option is wrong, while the question is simply unanswered. Not drawing it is no reason to stay silent, so `aria-invalid` goes on the element assistive software meets, the same one `describedTarget()` already picks. - **`nldd-form` marks a field invalid the moment the platform says so.** Without something joining `setValidity` and the attribute, a control whose validation list refused a value blocked the submit while nothing on screen said why. The browser already picks the moment, firing `invalid` on every failing control on submit, so the form listens in the capture phase. From that first verdict the mark follows the value both ways, and every field is judged at once rather than only the ones that failed. The native bubble goes with it: Safari put its own tooltip above the field carrying the text that already stood under it. Cancelling that event also cancels the browser's move to the first failing field, so the form makes it itself, once per round: without it a failed submit leaves focus where it was, which for anyone not looking at the screen is indistinguishable from nothing having happened. A reset takes the marks off again along with the values, because a reset fires no `input` and a field would otherwise keep a mark over a value the user just cleared. - **`required` where it was missing.** `nldd-dropdown`, `nldd-search-field`, `nldd-combo-box`, `nldd-checkbox`, `nldd-switch`, `nldd-checkbox-field`, `nldd-switch-field`, `nldd-segmented-control` and `nldd-toggle-button-group` did not have it. On a choice group it goes to the items, where the platform reads it: one required radio makes the whole group required and the browser writes its own message in the user's language. Radio mode only, because the same attribute on a checkbox means that box has to be ticked and HTML has no way to say "at least one of these"; `aria-required` goes on regardless and DEV says that nothing enforces it. `nldd-number-field` and `nldd-stepper` stay without: their value defaults to 0, so an empty one does not exist there. - **`pattern`, `minlength` and `maxlength` on the text fields.** No input had them, so consumers fell back on their own JavaScript for something HTML has done for fifteen years, blocking the submit included. They are on `nldd-text-field`, `nldd-password-field`, `nldd-search-field` and `nldd-combo-box`. The multi-line field gets the two lengths only, because HTML has no `pattern` on a textarea. - **`width="fit-content"` on `nldd-date-field` and `nldd-time-field`.** Both hold room at their end for a validation icon, so the field does not resize the moment it turns valid or invalid. Where a field is never validated that room is a visible gap. `fit-content` keeps only the air the text needs and takes the full slot back the moment a validation state does arrive, growing at the trailing edge so the date stays where it was. Opt-in, because only the consumer knows whether a field is ever validated. - **`type="form"` on `nldd-list`, for rows you fill in rather than walk through.** A row of a label and a field, a switch, a menu button, or a value you cannot change. Semantically the same as `list` and visually unchanged; what falls away is the keyboard of a list you walk through. No arrow navigation and no tab stop on the row: Tab goes straight to the controls, in source order. Rows that ARE actions contradict the type and warn in development. SwiftUI draws the same line between `List` and `Form`. - **Escape, from a control back onto its row.** One level up, where Shift+Tab is one step back. It works wherever you are in the row and puts the arrow keys back in reach. It claims the key at exactly that one level: on the row it claims nothing, so Escape carries on to whatever holds the list, and a control showing a menu keeps the first press for closing that menu. - **`line` on `nldd-timeline-track-cell`, for a step that opens a group.** A row that opens a group of its own is not the end of the going, so it drew an open track downward while there was progress below it. `line` names the halves you have covered; the other half is track still ahead, and `none` covers neither. It is about fill, not place: which halves are drawn stays with `position`, except that calling a half covered draws it. - **An inline `svg` in the slot of `nldd-image`.** Only an `img` and a `picture` were styled to fill the media box, so a drawing landed there at whatever size it brought along. It gets the same reset now, without `object-fit`, which does nothing on an svg because its own `viewBox` does the fitting. A drawing gets the box, the ratio and the caption a photo gets, and keeps its own colors. - **`no-tab` on six more components.** `nldd-link`, `nldd-checkbox`, `nldd-radio-button`, `nldd-switch`, `nldd-toggle-button` and `nldd-avatar` carry the property `nldd-button` and `nldd-icon-button` already had: `tabindex="-1"` on the control in the shadow root, so a container that runs its own focus can take it out of the tab order. ##### Changed - **The title of `nldd-toolbar-title` and `nldd-top-title-bar` is semi-bold instead of medium.** Both sizes of the toolbar title, and in the title bar both the plain title and the one step smaller title that carries a subtitle. The subtitles stay regular. ##### Fixed - **`required`, `pattern`, `minlength` and `maxlength` now stop a submit.** They are handed to a control inside the shadow root, and that control is not a member of the form around the component: the form sees the host, the host sees the control, and nothing joined the two. The form-associated mixin reports the control's verdict as the host's own now, which fixes every input that renders one. A group (`nldd-segmented-control`, `nldd-toggle-button-group`) and the editors have no such control and are unchanged. Two consequences: a form that used to go through with an empty required field now stops, and outside an `nldd-form` the browser shows its own bubble, the way it does for any form control. - **A validation list and a native constraint no longer erase each other.** `setValidity` writes the whole set of flags at once, so whoever wrote last won: a list reporting its failing rule cleared the field's `required`, and the next render cleared the rule. Both go through `setCustomValidity` now, which the mixin keeps beside the native flags rather than in place of them. A plain native input answers to the same method, so a list outside this system's inputs still works. - **A validation list keeps listening after its field is moved.** Taking the element out of the DOM and putting it back, which is what a consumer portalling a sheet to `document.body` does, takes the input listener with it. Reconnecting found the same control as before and concluded there was nothing to wire up, leaving the list reacting to nothing. It now also checks whether it still has an observer. - **An empty `name` or `width` no longer reaches the DOM.** Every input carried `name=""` and half of them `width=""`, on every instance in every consumer app. Twenty-two components reflect them only when they hold something now. - **The input of `nldd-number-field` keeps its minimum width.** Three rules hung on `:host([width])`, and that selector always matched, because an empty `width` reflected to the DOM. Underneath sat a real bug: the base rule gives the input a `min-width` and the always-matching rule overwrote it with 0, taking `hide-spin-buttons` down with it, which sets 80 pixels and got 0. - **The description of a field reaches the screen reader.** It arrived at 3 of the 26 inputs, measured against the accessibility tree: the field wrote `aria-describedby` on the input in its own shadow root while the ids it named stood in the document, and an IDREF is looked up in the tree of the element that carries it. The elements themselves go through now, via `ariaDescribedByElements`, and a mixin hands the question down through a wrapper to the component that knows which element inside itself is the control. - **The website title in `nldd-top-navigation-bar` is a 24 by 24 target.** WCAG 2.5.8 asks that of anything you click, and this one was 20 high on a phone and 23 at medium. The row around it was 24, but that came from padding on the bar, which does nothing for the target inside it. The title carries its own minimum now, in both directions and whether or not it links anywhere. - **Avatars in a group overlap by a fifth of their diameter, whatever that diameter is.** The overlap was a flat 8 pixels: 29% of the avatar at size 28 and 8% at size 96, so the same component read as a tight stack at one size and a row of separate circles at another. A fifth is what 8 was at the default size of 40, so a group at that size looks exactly as it did. - **A form section stands 24 pixels from its neighbors on both sides.** It carried padding, and padding does not collapse, so it added itself to the 16 the form already puts under the preceding child. It is a margin now: 16 and 24 beside each other are 24. On both sides rather than only above, because with a top margin alone the distance under a section depended on what happened to follow it. - **The buttons of a form stand 24 pixels below it.** Also in a form without sections, where the actions used to be one more gap like everything else. The end of a form is the end, not one more question. - **A section heading sits 8 pixels above its fields instead of 16.** It belongs to what is under it, and the same distance on both sides left it floating between the group above and the group below. - **A form section spaces whatever you put in it.** Its children leaned on a rule that only recognized one field followed by another, so a button or a paragraph inside a section got no room and every caller added it themselves. The section uses the same rhythm as the form around it now, and asks whether something visible follows rather than what the thing is. - **A field label sits four pixels above its control instead of three, in the flat type rather than the tight one.** Three was close enough to read as attached and far enough to look like a mistake, and the tight leading pulled a wrapping label onto itself. - **One gap between everything in a form.** Two fields in a row used to sit ten pixels apart instead of sixteen, and it took two container queries and four `:not()`s to say so. It bought six pixels per pair, while a toggle group or a combo box stood ten pixels from the next label. A field, a section, a button, a paragraph: whatever follows another thing stands the same distance from it now, and a section or the buttons of a form stand a step further off. The three gap tokens are two, named after what they separate: `--semantics-forms-fields-gap` and `--semantics-forms-sections-gap`. `--semantics-forms-gap` and `--semantics-forms-gap-tight` are gone; nothing outside this package used either. - **`nldd-form-section` no longer draws lines around itself.** A section carries a heading, and a rule above and below says the same thing a second time. What gave it away was the suppressing: no line on the first child, none between two adjacent sections, none on the last, and that last exception did not work, so every form with a submit button had a rule drawn right above that button. The padding stays and is what groups. - **A `close` from an overlay stays with the overlay it came from.** `nldd-sheet`, `nldd-window`, `nldd-modal-dialog` and `nldd-popover` fired `close` with `bubbles: true`, so an overlay inside another one reached the listener on the one around it. A consumer binding `(close)` on the outer overlay got the inner one's close as well and tore down what was still open. The event no longer bubbles. Anyone checking `event.target` to work around this can stop. - **Escape closes what is on top, and nothing behind it.** A combo box, a token field and anything on `nldd-popover` closed on Escape and let the same press travel on, so a sheet or window behind them went too. They stop the press the way `nldd-menu` already did. With nothing open they claim nothing, so Escape still reaches the sheet. - **A list no longer takes the arrow keys out of a control in one of its rows.** The keys were claimed by whichever list they bubbled through. A combo box in a row lost its menu on ArrowDown, and a text field lost its caret on Home. The list stands down now while the key comes from a control the row holds; what the row IS keeps answering, since a link, button or segment is not a control it holds. - **A list is one tab stop from the outside again.** The current row hands its controls a place in the tab order, and those places stayed handed out after focus left the list, so tabbing back in dropped you into the control you had left rather than on the row. Leaving the list parks them again. - **The clear and picker buttons of a combo box sit in the middle again.** Both are wrapped in a plain block, so the wrapper took the height of the line box it inherited and the button hung at the top of it: centered in a story, high in an app whose body sets a taller line-height. They are flex now, like the file, time and date fields. - **A row let go outside itself no longer stays lit.** `nldd-list-item` and `nldd-list-item-segment` showed the press on `pointerdown` and cleared it on a `pointerup` on the row, which is not where the event lands when you release off the row. The release is watched on the window now, for as long as a press is running. - **`nldd-box` and `nldd-card` take the width they are given.** An `nldd-cell` lines its content up on the left rather than stretching it, so a box or a card inside one shrank to its content: in a narrow cell that came down to a single letter with the title running vertically. As a block that already happened, as a flex item it did not. - **The scroll mode follows a split view that arrives late.** A consumer that renders its split view behind a condition, such as a loading state or an empty state, takes it away and puts it back. While it was gone the derivation said `root`, and the split view that comes back measures itself as what it already was, so it announces nothing: every pane kept scrolling as one document until a reload. A layer arriving or leaving now re-runs the derivation. - **A bar in a sheet keeps its back button.** A split view hides the back button of a bar inside a pane, because the menu beside you is the way back. That hiding traveled down the whole pane, so a sheet standing in one lost the back button of its own bar. A sheet starts that context over. - **The warning about a control a row cannot reach now fires, and names the control.** It ran while the list rendered, and a slotted custom element has its shadow root by then but has not rendered into it, so the check found nothing. It waits for the control to have rendered, runs again for rows added later, and reports once. It stays quiet where nothing is wrong: a segment, a disabled or negatively-tabindexed control, and anything the component hides itself. - **Lit no longer reports an update scheduled after an update completed.** `nldd-list`, `nldd-list-item`, `nldd-collection`, `nldd-menu`, `nldd-menu-item` and `nldd-navigation-split-view` set state from `firstUpdated`, from the first `slotchange` or from `updated`, which is a dev-mode warning on every page holding a list or a menu. What the first render needs is read from the light DOM before it now, and what can only be read afterwards is set out of the update cycle. - **A branch row that starts collapsed no longer warns.** The row reads an absent `expanded` as collapsed and emits `aria-expanded="false"`, so the warning asked for something that was already there. The warning about `slot="children"` outside a `type="tree"` list stays, and reports once per row instead of on every change. ### [`v0.8.83`](https://github.com/MinBZK/storybook/blob/HEAD/CHANGELOG.md#0883-2026-08-20) [Compare Source](https://github.com/MinBZK/storybook/compare/v0.8.82...v0.8.83) ##### Highlights - **`nldd-text`, a component for body text.** New, and the one piece of typography that had no component: every line that is not a heading and not a block of prose. It offers `size` (xxs through lg), `weight`, `line-height`, `color` and `horizontal-alignment`, all of them names for what the token scale already holds, so it cannot invent typography. Color follows the content channel a row, menu or table sets, so a line travels with the row it sits in. Reach for `nldd-title` for a heading, `nldd-rich-text` for prose with its own rhythm. - **Every list answers to the arrow keys, and a row may hold more than one control.** One list now behaves like the next: ArrowUp and ArrowDown walk the rows there is something to do with, Home and End jump to the ends, and the list as a whole is one tab stop. Within the current row Tab walks its segments and the controls in its cells, while those same controls in the other rows stay out of the tab order. Two types mean something else by the same keys, and you can see which before you touch one: a `listbox` runs its keyboard from its search field, and a `reorderable` list moves rows. - **`current` beside `selected` in a list row.** `current` is the row you are on, `selected` a row you picked, and a list can show both. At rest they look the same, so existing lists do not change. The difference shows when focus is in the row: only the current row takes the accent fill, and it does so from a nested `nldd-list-item-segment` too. Row states now sit on the same ladder as button states, with a rest, hover and press step for each, under the names the buttons already use. Seven tokens more. - **A color the system does not know, on a badge and an icon.** Some colors are not a category but a fact: the jacket of a cable is blue whatever the house style does. `custom-color` on `nldd-badge` and `nldd-icon` takes any CSS color value and wins over `color`, so the vocabulary of `color` stays closed and one search shows where an app steps outside the palette. On the badge, what sits on the color turns white or black, whichever contrasts better. - **Thirty icons added, three redrawn.** Added: `cpu`, `gpu`, `memory-chip`, `psu`, `pci-card`, `transceiver-module`, `rack-server`, `rack-servers`, `external-hard-drives`, `ssd-hard-drive`, `external-hard-drive`, `network-switch`, `kvm-switch`, `power-plug`, `lightning`, `snowflake`, `boxes-3`, `clipboard-bullet-list`, `kanban-columns`, `printer`, `shield-arrow-right-arrow-left`, `waving-crossing-lines`, `circle`, `circle-circle`, `minus-circle`, `circle-light`, `circle-circle-light`, `check-mark-circle-light`, `clock-light` and `slash-circle-light`. Redrawn: `shield`, `shield-check-mark` and `shield-lock`. ##### Breaking - **`database-disabled` and `database-unavailable` are now `disabled-database` and `unavailable-database`.** The set puts the state in front everywhere else: `new-namespace`, `no-priority`, `checked-small`. Those two trailed it, and were the only aliases that did apart from the `-light` weights, which name the drawing rather than a state. Both still point at `cylinder-split-slash`. Consumers on an old name change one string. - **`nldd-list-item-action` is now `nldd-list-item-segment`.** The component was not always an action: without `href`, `button` or `checkbox`, and inside a listbox, it renders as a plain container. What it always is, is a piece of the row with its own hit area and its own fill, which is what the docs had been calling it all along. Consumers rename the element and the import path, and the attributes stay the same. The host classes follow: `has-leading-segment` and `has-trailing-segment`. - **Every list answers to the arrow keys, and `arrow-navigation` is gone.** Whether the arrow keys work is what a reader may assume about every list, not a setting per list, and as a setting it was unknowable from the outside. Consumers drop the attribute. Leaving it in does nothing, and warns in dev. - **`nldd-box` paints with `background`, like every other surface.** The attribute was `variant`, which in this system is the word for what a component is, not for which surface it draws. `nldd-app-view`, `nldd-page`, `nldd-split-view-pane` and `nldd-card` all call that `background`. So `variant="base|tinted|critical"` is now `background="base|tinted|critical"`. One thing to know: a box starts on `tinted` where a card starts on `base`, because a box stands out from the page and a card sits on it. Consumers change one string per box. - **The plus badges are named after what they make.** `add-namespace` is now `new-namespace`, `add-k8s` and `add-kubernetes` are `new-k8s` and `new-kubernetes`, and `add-text-document` is gone in favor of `new-text-document`. A namespace, a cluster and a document are made, not added. `add` stays for putting something that exists into something else, which is why `add-plugin`, `add-user`, `add-location` and `add-emoji` are unchanged. Consumers on an old name change one string. ##### Added - **Eighteen icons for a data center: the hardware and the network.** `cpu`, `gpu`, `memory-chip`, `psu`, `pci-card`, `transceiver-module`, `rack-server`, `rack-servers`, `external-hard-drives`, `ssd-hard-drive`, `external-hard-drive`, `network-switch`, `kvm-switch`, `power-plug`, `lightning`, `snowflake`, `waving-crossing-lines` (`network-patch-mapping`) and `shield-arrow-right-arrow-left` (`firewall`), with the aliases `server`, `servers`, `memory`, `ram`, `processor`, `power`, `cooling`, `airco`, `hard-drive`, `nic`, `network-interface-card`, `sfp` and `energy`. Two are named after what they draw rather than what they came in for: `network-switch`, because `switch` on its own is the verb, and `snowflake`. `arrow-left-right` gets that verb as an alias: `switch` and `swap`, for moving between two things. There are two racks: `rack-servers` drawn flat on for a diagram, and `rack-server` at an angle for a row, where three stacked bays turn into three stripes. `server` points at the angled one, `servers` at the flat one. `waving-crossing-lines` draws the runs themselves, sweeping from one side to the other and crossing on the way, rather than the nodes they connect, which is what a patch panel looks like from the front. Two of them are a pair: `external-hard-drive` is one unit, `external-hard-drives` the stack of three, aliased `storage`. The firewall shield carries the traffic it filters, two arrows passing each other, so it says what it does instead of only saying "safe". - **Eight icons for getting things done.** The three task states read as one series: `circle` for to do and `circle-circle` for doing, beside the `check-mark-circle` the set already had, aliased as `to-do`, `doing` and `done`. `minus-circle` is in the set as well: the same ring with a bar across it. `kanban-columns` draws a board of work in columns (`kanban`), and `circle-grid-2x2-top-left-check-mark` gets the alias `all-tasks`. `circle` doubles as `no-priority`: the same ring as `low-priority` through `high-priority` with nothing in it, so that column keeps reading as a count from zero up. Four of the state icons come in a light weight drawn for 32 pixels: `circle-light`, `circle-circle-light`, `check-mark-circle-light` and `clock-light` keep the 2-pixel line on a 32 view-box instead of a 24 one, so the line stays 2 pixels at the size they are drawn for rather than growing to 2.7. Use them where a state icon leads a row at 32, and reach for the regular set below that. Aliased as `to-do-light`, `doing-light` and `done-light`. - **Four icons outside those two groups, and one alias.** `boxes-3` for a stock of things you own (`inventory`), `clipboard-bullet-list` for that same list read as a checklist (`inventory-alt`), `printer` for printing (`print`), and `slash-circle-light` in the same light weight as the state icons (`blocked-light`). `brackets-ellipsis` gets the alias `namespace`, the meaning the brackets with the ellipsis carry wherever the console uses them. - **`color="inherit"` on `nldd-badge`.** Fill with the content color around it (`--context-content-color`, otherwise `currentColor`), the meaning it already has on `nldd-avatar`, so a badge travels with the line it sits in. - **`disabled` works on a row and on a link.** A segment could be switched off and a row could not, while a row can just as well be the button or the checkbox. A `button` or `checkbox` row takes the real off state and dims. A link gets `aria-disabled` and a blocked click, because an anchor cannot be switched off the way a button can, and that goes for a link segment as well: there `disabled` used to do nothing at all. Hover and press do nothing there, and the arrow keys skip it: a disabled control is not focusable, so a stop there would swallow the key. That already applied to a disabled segment, so a row whose only segment is off is no longer a stop either. - **A `current` segment makes its whole row current.** A row built from segments has no link of its own, so `aria-current="page"` belongs on the segment that holds the link. The row reads it off its own segments and paints itself, so it is written once. Reading, not writing: the attributes stay the consumer's. `current` also appears in the controls of `nldd-list-item` and `nldd-list-item-segment` now. - **A ticked row is painted, the same as a ticked segment.** A row that is the checkbox itself took no fill, so the same list looked different depending on where the tick sat. A ticked row is a row you picked, so it paints what `selected` paints, with the same hover and press steps. Only together with `checkbox`. - **A segment stays lit while what it opened is on screen.** `expanded` on a segment set `aria-expanded` and turned a chevron, but left the segment looking untouched, so a menu hanging off one row floated over the list without saying which row it belonged to. It now paints the same fill as a picked row (`--components-list-item-is-expanded-background-color`, two steps above hover), and after the hover rule, so the pointer cannot dim it while the menu is open. - **`no-tab` on `nldd-button`.** The same property `nldd-icon-button` already had: take the button out of the tab order (`tabindex="-1"` on the button in the shadow root) because a container around it manages focus itself. An `nldd-list` sets it on the buttons in the rows that are not the current one. Mouse and script still reach the button. - **Enter follows the row in a tree.** It activates the row's own link or button, or on a branch built from segments the first one that is not the chevron. Space folds, the same as Left and Right. Enter used to fold, so a branch you could reach with the arrow keys could not be opened without hunting for a segment with Tab. - **`readonly` on `nldd-combo-box`.** The value stays readable and submits with the form, and the control that would change it is gone rather than dimmed. - **`keyboard` and `enter-key` on the text fields.** They set `inputmode` and `enterkeyhint`, so a phone shows the right keys and the right label on the return key. ##### Changed - **`inbox` is now `tray`, and a set of aliases moved with the way the set names things.** The file says what it draws, a tray with a slot, and `inbox` stays as the alias for what it is used for. New aliases: `time-light` on `clock-light`, `locked-database` on `cylinder-split-badge-lock`, `checked-text-document` on `file-text-badge-check-mark`, `companies` on `apartment-building-2`, `add-extension` and `add-module` on `puzzle-piece-badge-plus` (the plain piece already answered to all three words, the plus badge only to `plugin`), and the directory vocabulary is carried through the folders it was missing from: `new-directory`, `folders`, `directories`, `open-folder` and `open-directory`. - **The three shields are redrawn on one silhouette.** `shield`, `shield-check-mark` and `shield-lock` shared a pointed crest built from arcs, and now stand on the same flat-topped, rolled-edge shield as `shield-arrow-right-arrow-left`. That shield is wider at the top and flatter, and that is the reason for it: more fits on it. A check mark, a lock or two arrows passing each other need room, and on a pointed crest such a figure runs into the slanted edges. Four shields that differ only in what they carry, rather than a family that splits the moment a fourth arrives. - **A read-only field takes its controls away instead of dimming them.** A dimmed control invites a click that does nothing. The field keeps its value and its label, and drops the spinner, the picker and the clear button. - **A subtitle in `nldd-title` is one step larger.** It sat two steps under the title, which read as a caption rather than as the line that belongs to it. ##### Fixed - **The text on a colored fill is picked on luminance now, and clears 4.5:1.** `--semantics-content-contrast-color` decided between black and white on OKLCH lightness, which is perceptual and parts ways with the luminance WCAG measures, so a saturated mid blue got white text at 3.68:1. The threshold is now the relative luminance where black and white give the same contrast, Y = (sqrt(21) - 1) / 20, which makes the picked color at least 4.58:1 whatever it lands on. Where the browser has `contrast-color()`, that answers instead. This reaches every component that puts content on a fill it was handed: `nldd-badge`, `nldd-avatar`, `nldd-step-indicator`, `nldd-keyboard-shortcut` and the timeline cell. - **Switching a row off now moves the tab stop with it.** A row that carried the list's single tab stop kept it after `disabled` landed on it, so Tab reached a row that answers to nothing. The arrow keys already skipped it, and an unrelated change happened to repair it, which is the worst kind of bug: it fixes itself while you look at it. The row re-runs the roving when its own `disabled` changes, and the list watches the attribute too. - **A read-only combo box no longer announces a list it cannot open.** The picker button and the menu were already gone, but the input kept `role="combobox"` with `aria-haspopup="listbox"`, `aria-autocomplete` and `aria-expanded`. It is a text field with a value in it, and it now says so. - **`color="inherit"` on a badge or an avatar could paint white on white.** The fill came from the content channel around it, while the contrast flip that decides between black and white text read the inherited `color`. Those are the same value in most places, so it went unnoticed until a row set the channel to white over dark text: the badge then took a white fill and, from the dark inherited color, white text as well. Both components now set their own `color` to the fill first, the way `custom-color` already did, so the flip is computed against what is actually painted. - **An overlay is where the app's layout context ends.** A sheet or dialog inherited the app's scroll mode, so a short page inside an overlay could float its sticky footer mid-screen. An overlay now starts its own context. </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Amsterdam) - Branch creation - "before 6am on sunday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zMS4wIiwidXBkYXRlZEluVmVyIjoiNDQuMzEuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->
Update dependency @nldd/design-system to ^0.8.83
Some checks failed
CI / pre-commit (pull_request) Successful in 1m58s
CI / release-scripts (pull_request) Successful in 11s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 49s
security-scan / Python SAST (bandit) (pull_request) Successful in 32s
CI / frontend-test (pull_request) Failing after 3m47s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 24s
security-scan / JS SCA (npm audit) (pull_request) Successful in 30s
security-scan / SBOM (trivy) (pull_request) Successful in 16s
test-build / build (backend) (pull_request) Successful in 2m11s
test-build / build (frontend) (pull_request) Successful in 2m4s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 19m42s
CI / e2e (pull_request) Failing after 25m17s
7032395da9
robbertbos force-pushed renovate/nldd from 7032395da9
Some checks failed
CI / pre-commit (pull_request) Successful in 1m58s
CI / release-scripts (pull_request) Successful in 11s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 49s
security-scan / Python SAST (bandit) (pull_request) Successful in 32s
CI / frontend-test (pull_request) Failing after 3m47s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 24s
security-scan / JS SCA (npm audit) (pull_request) Successful in 30s
security-scan / SBOM (trivy) (pull_request) Successful in 16s
test-build / build (backend) (pull_request) Successful in 2m11s
test-build / build (frontend) (pull_request) Successful in 2m4s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 19m42s
CI / e2e (pull_request) Failing after 25m17s
to 335a5a9248
Some checks failed
CI / pre-commit (pull_request) Successful in 2m14s
CI / e2e (pull_request) Failing after 53s
CI / release-scripts (pull_request) Successful in 8s
CI / frontend-test (pull_request) Failing after 5m28s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 50s
security-scan / Python SAST (bandit) (pull_request) Successful in 27s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 21s
security-scan / JS SCA (npm audit) (pull_request) Successful in 29s
security-scan / SBOM (trivy) (pull_request) Successful in 16s
test-build / build (backend) (pull_request) Successful in 2m9s
test-build / build (frontend) (pull_request) Successful in 2m1s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 20m10s
2026-08-31 02:07:27 +00:00
Compare
robbertbos changed title from Update dependency @nldd/design-system to ^0.8.83 to Update dependency @nldd/design-system to ^0.8.84 2026-08-31 02:07:28 +00:00
robbertbos force-pushed renovate/nldd from 335a5a9248
Some checks failed
CI / pre-commit (pull_request) Successful in 2m14s
CI / e2e (pull_request) Failing after 53s
CI / release-scripts (pull_request) Successful in 8s
CI / frontend-test (pull_request) Failing after 5m28s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 50s
security-scan / Python SAST (bandit) (pull_request) Successful in 27s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 21s
security-scan / JS SCA (npm audit) (pull_request) Successful in 29s
security-scan / SBOM (trivy) (pull_request) Successful in 16s
test-build / build (backend) (pull_request) Successful in 2m9s
test-build / build (frontend) (pull_request) Successful in 2m1s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 20m10s
to efa08fbcc2
Some checks failed
CI / pre-commit (pull_request) Successful in 2m12s
CI / frontend-test (pull_request) Failing after 5m24s
CI / release-scripts (pull_request) Successful in 9s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 51s
security-scan / Python SAST (bandit) (pull_request) Successful in 28s
security-scan / JS SCA (npm audit) (pull_request) Successful in 32s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 22s
security-scan / SBOM (trivy) (pull_request) Successful in 15s
test-build / build (backend) (pull_request) Successful in 2m9s
test-build / build (frontend) (pull_request) Successful in 2m0s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 20m8s
CI / e2e (pull_request) Failing after 24m49s
2026-09-01 02:07:20 +00:00
Compare
robbertbos changed title from Update dependency @nldd/design-system to ^0.8.84 to Update dependency @nldd/design-system to ^0.8.85 2026-09-01 02:07:23 +00:00
robbertbos force-pushed renovate/nldd from efa08fbcc2
Some checks failed
CI / pre-commit (pull_request) Successful in 2m12s
CI / frontend-test (pull_request) Failing after 5m24s
CI / release-scripts (pull_request) Successful in 9s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 51s
security-scan / Python SAST (bandit) (pull_request) Successful in 28s
security-scan / JS SCA (npm audit) (pull_request) Successful in 32s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 22s
security-scan / SBOM (trivy) (pull_request) Successful in 15s
test-build / build (backend) (pull_request) Successful in 2m9s
test-build / build (frontend) (pull_request) Successful in 2m0s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 20m8s
CI / e2e (pull_request) Failing after 24m49s
to 9134f31b7a
Some checks failed
CI / frontend-test (pull_request) Failing after 6m10s
CI / release-scripts (pull_request) Successful in 8s
CI / pre-commit (pull_request) Successful in 8m45s
security-scan / Python SCA (pip-audit) (pull_request) Successful in 47s
security-scan / Python SAST (bandit) (pull_request) Successful in 28s
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 20s
security-scan / SBOM (trivy) (pull_request) Successful in 13s
test-build / build (backend) (pull_request) Successful in 1m55s
security-scan / JS SCA (npm audit) (pull_request) Successful in 3m51s
test-build / build (frontend) (pull_request) Successful in 1m59s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 16m47s
CI / e2e (pull_request) Failing after 13m14s
2026-09-04 02:07:22 +00:00
Compare
robbertbos changed title from Update dependency @nldd/design-system to ^0.8.85 to Update dependency @nldd/design-system to ^0.8.86 2026-09-04 02:07:25 +00:00
Some checks failed
CI / frontend-test (pull_request) Failing after 6m10s
Required
Details
CI / release-scripts (pull_request) Successful in 8s
Required
Details
CI / pre-commit (pull_request) Successful in 8m45s
Required
Details
security-scan / Python SCA (pip-audit) (pull_request) Successful in 47s
Required
Details
security-scan / Python SAST (bandit) (pull_request) Successful in 28s
Required
Details
security-scan / Filesystem scan (trivy fs) (pull_request) Successful in 20s
Required
Details
security-scan / SBOM (trivy) (pull_request) Successful in 13s
Required
Details
test-build / build (backend) (pull_request) Successful in 1m55s
security-scan / JS SCA (npm audit) (pull_request) Successful in 3m51s
Required
Details
test-build / build (frontend) (pull_request) Successful in 1m59s
test-build / build (pull_request) Successful in 0s
CI / backend-test (pull_request) Successful in 16m47s
Required
Details
CI / e2e (pull_request) Failing after 13m14s
Required
Details
Some required checks were not successful.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin renovate/nldd:renovate/nldd
git switch renovate/nldd
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
robbertbos/waggle!341
No description provided.