Changes to @umriss-ui/core
This document describes what changes for callers of the package: exports, props, behaviour.
The numbers. The middle digit rises when something is added; the last one
when something is repaired. As long as the first digit is 0, no number
promises compatibility — which is why a rule stronger than the figure applies as
well: whatever changes existing behaviour stands under a heading "Changed" of
its own, no matter which digit rose. Whoever reads only one section before an
upgrade reads that one.
From 0.34.0 the packages share one number - five, and six from 0.40.0, when the dashboard joined: they are released together, and a package without a change of its own still gets a heading for it.
Names read forwards. Where an entry below describes a name that has since been renamed, it is named here as it is called today; string literals, and names of things that were removed rather than renamed, stand as they stood.
The count started again at publication. 0.1.0 is the first version for the
registry. Before it, the package counted up to 0.10.0 inside this repository
without ever being published; those entries stand below under internal
numbers (internal 0.9.0), and where another document of this repository names
a core version from before the first publication, it means that internal number.
Of those, internal 0.1.0 to internal 0.3.0 were reconstructed afterwards — the
package stood at 0.1.0 the whole time, because it had exactly one caller and that
caller lay in the same repository — and are grouped by unit of delivery, not by
commit.
0.40.0 – Words for the dashboard (Oct. 2026)
Added
- Wording for
@umriss-ui/dashboard: thedashboard…entries - the editing bar, a widget's menu and head, the size names, the catalogue, the built-in heading, a failed widget and every announcement - in English and German. An application that passes a completeWordingof its own adds them as well.
0.39.0 – Toasts that stay, and one provider (Oct. 2026)
Changed
- A toast that stays never gives way to the limit. A toast with
duration: 0and aloadingtoast are no longer pushed out by newer ones, and they do not count towardlimit: the limit caps only the toasts that leave by themselves, the oldest of them giving way as before. A failure that stays until it is read is no longer lost to a burst of confirmations, and aloadingtoast stays untilupdategives its outcome. The limit is applied when a toast arrives;updatepushes nothing out, and aloadingtoast turned into its outcome counts from the next arrival on. So the deck may hold more thanlimittoasts: every lasting one beside up tolimittimed ones. Check pages that sent many lasting toasts and relied on the limit to clear them.
Added
- A second
ToastProvideris reported in development. An application has oneToastProvider, at its root; two draw two decks at the same place, one over the other. A provider that mounts while another stands in the same document now writes oneconsole.warnin development. Nothing changes in production.
0.38.0 – Holding the toasts back, and a deck that glides (Oct. 2026)
Added
heldonToastProviderholds the toasts back for a while: for a surface the toasts must not cover, where there is no room beside it. While it is true no toast is seen, heard, pressed or reached by the keyboard, Alt + T goes to the page, and what lies under the deck's place can be seen and used as if no toast stood; a focus in the deck goes back where it came from, as with Escape. Nothing is lost, as it would be by dismissing: no countdown runs, a toast that arrives waits, andtoast(),update()anddismiss()work as always (dismiss()and the limit close a toast at once, with itsonClose; holding calls none). When it is false again every toast comes back at the deck's current place with its entrance and the time it had left; the ones that arrived meanwhile are heard then, the others not a second time. Defaultfalse; the example "Hold the toasts" on the Toast page shows it.type ToastProviderPropsis exported, like the props of the other components.--u-toast-width, the toast's width,360pxby default, in the Toast token group beside the four distance tokens. A plain length: read from the computed style it tells whether the deck has room beside a surface, and set on:rootit makes the toasts wider or narrower. Up to 600 px wide the toast keeps the window's width less the side tokens.
Changed
The deck glides when its distance changes. A change of
--u-toast-inset-top,-bottom,-startor-endwhile a toast is seen moves the deck to its new place in--u-duration-mediumwith--u-ease-out, as its height moves; it jumped. It glides also when the change comes in the moment a modal dialog opens or closes, so the region moves into or out of it: the deck starts where it was seen. It moves at once under reduced motion, while no toast stands (a first toast stands at its place), while held and when a hold ends; a token set again to its value, in every frame if need be, moves nothing, and a deck whose distance stays the same stands still when a dialog opens or closes, as before. A change ofpositiondoes not glide.An open deck at the top edge lays the lower card above. Open, the card lower on the screen now lies above the one over it wherever the deck stands, so no card's shadow falls on the face of the next; at the top it was the other way round. The order turns once the cards stand apart, after
--u-duration-medium, and at once on closing and under reduced motion. A closed deck is ordered as before.
0.37.0 – The toasts' distance from the window's sides (Oct. 2026)
Added
--u-toast-inset-startand--u-toast-inset-end, the toasts' distance from the window's sides. The default is the distance they had:--u-space-5,--u-space-4up to 600 px wide. Set on:root, the start one moves the*-startpositions - beside a navigation rail, at the page's gutter - and the end one the*-endpositions; on a phone, where the toast takes the window's width, each is the room at its side at every position. The center positions stay in the middle of the window, and in a right-to-left page start is the right side. With--u-toast-inset-topand--u-toast-inset-bottomevery edge of the window has its token. A rule on the region'sdata-alongis no longer needed, and was never safe (ADR-0045).
0.36.0 – Toasts stand above a modal dialog (Oct. 2026)
Changed
Toasts stand above an open modal dialog and can be used there. A toast shown while a
Modal,Drawer,ConfirmDialog,CommandPaletteor a<dialog>of your own opened withshowModal()was open lay behind it and was inert; it is now seen, pressed, reached by Alt + T and read out. It stands at the same place in the window, and a toast that was standing does not move, restart its entrance or its clock, or callonClosewhen a dialog opens or closes behind it. Escape in the deck gives the focus back and leaves the dialog open (ADR-0063).Where the toast region stands in the DOM. While a modal dialog is open the region is a descendant of the one opened last; when it closes the region returns to the provider's
portalTarget, else the body. A dialog opened withshow()leaves it where it was. Check a selector or a test that expects the region under<body>, or that takes the one child of an open<dialog>for its content, and dialogs with controls in the corner where the toasts stand: they are covered there, as the page is.Inside a modal dialog the toasts and the tooltips are ordered by the top layer, not by
--u-z-toastand--u-z-tooltip, so an override of either decides nothing there; a tooltip lies above a toast and a popover under it, as in the page. Outside a dialog the tokens order them as before and nopopoverattribute is set. Where the browser has no Popover API both are fixed boxes inside the dialog, ordered by the tokens, and the deck moves with a dialog that transforms itself.
Fixed
A tooltip in a dialog that transforms itself stands at its trigger. It stood off by the dialog's transform.
The first toast is heard where a
portalTargetis set. With aportalTargetgiven as a function that reads an element above theToastProvider, the toast region stood in the body until the first toast and moved to the target together with it, and a live region that arrives with its text is often not read out. The region now stands in the target from the start.
0.35.0 – React 19 is the floor, and an import pays for what it uses (Oct. 2026)
Changed
React 19 is required. The peers
reactandreact-domare>=19;0.34.0is the last version that serves React 18 (ADR-0062).refis a prop of every component, and none is aforwardRefobject. A component is a plain function that takesrefwith its props, and its props type says so: it extends the new exported typeRefProp<Element>, with the element the component renders at its root. Arefworks as it did; arefof the wrong element type is now a type error, and code that looked for$$typeofon a component no longer finds one.The package ships ES2022 syntax. An application that serves older browsers transpiles it itself.
Fixed
A virtualised tree that was scrolled stays where it was scrolled. Once its active node had been moved by key, the tree pulled the view back to that node on every render, so a wheel could not leave it. The view now follows the active node only when the node moves.
A virtualised tree scrolls its whole length inside a container of fixed height. Its filler rows were squeezed to nothing there, and the scroll area was only as high as its window.
Otherwise
An import pays for what it uses. Every component was a call at the top of its module, which a bundler has to keep with everything it reaches, so one
Buttonbrought the package. Minified and gzipped,Buttonalone is now 5.2 kB where it was 43.5 kB,Select9.4 kB, and an import that uses nothing leaves 1.1 kB. A check on the built package holds it.A key in a large tree costs what it costs in a small one. The flattening was computed again for every move of the active node; at 100,000 nodes a key took 14 ms and now takes 0.3 ms.
useVirtualrenders its caller only when the window of rows changes. It used to render on every scroll event.
0.34.0 – The Dock and the alarm list's words leave (Oct. 2026)
From this version all five packages carry one number and are released together.
Changed
--u-color-accent-activeand the wordingasOfUnknownare removed. No stylesheet read the token and no component read the wording; setting either did nothing. A theme or a wording that still carries them loses nothing (the type checker reports the wording key as unknown).The alarm list's wording keys are removed: the alarm, priority, lifecycle, snooze, flood, chatter, availability and alarm-column keys. A wording that still carries them is ignored at runtime and reported by the type checker as an unknown key.
verdictAlarmand the limit model are unchanged.Dockis removed, withDockProps,DockToolandDockPlace, the eight--u-dock-*tokens and the wording keysdockTools,dockGrip,dockPlace,dockPlacedanddockNoPlace(a wording that still carries them is ignored at runtime and reported by the type checker as an unknown key). Nothing in the workspace used it. A caller that needs a strip of tools over a surface builds it fromButton,Tooltipand the glyphs;GripGlyph,GridGlyphandMeasureGlyphstay. ADR-0013 and ADR-0014 are marked withdrawn.The tree's transition functions are no longer exported from the entry:
move,toggleCheck,expand,collapse,keysBetween,toggleAll,setChecked,setActive,setAnchor,typeaheadTarget,pathToandrevealPath.treeModel,allBranchesandduplicateKeystay, anduseTreeis what a caller drives a tree with. The same holds forremainingCharsandclampedHeightofTextarea. No type changed.
Fixed
- An announcement that waits past its page no longer throws. The library's
voice speaks after a rest of 150 ms; where the document was gone by then - a
test's environment torn down right after a combobox, a multi-select, the
command palette or a copy spoke - the timer ran into
document is not definedand a test run ended with an unhandled error. It says nothing now.
0.33.0 – The library's one voice, and React 18 proved (Oct. 2026)
Added
announce(text, at?): the library's one voice - the polite live region the combobox, the multi-select and the command palette speak through - for another package or an application that has something to say without a visible change. A later call within 150 ms replaces an earlier one; the same words again are heard again;atis the element that speaks, so that inside a modal<dialog>it is the dialog's own region.The unit tests run against React 18.3.1 and 19 in CI, and the fields' widths are rendered to a string on a server in a test (
fieldWidth.test.tsx).availabilitySnoozed(until, by, reason?): a third, optional argument; the English and German sentences append: ${reason}for a non-empty reason ("Snoozed until 14:00 by M. Weber: valve being replaced" / "Zurückgestellt bis 14:00 von M. Weber: Ventil wird getauscht"). A wording of two parameters still fits the type and keeps working, with no reason in its sentence.copiedRows(count, formatted),copiedCellandcopyFailed: what the table says to a screen reader after Ctrl/Cmd+C ("3 rows copied", "Cell copied", "Could not copy"), in English and German. A wording that leaves them out fails its type, as with any new key.
Changed
Every entry begins with
"use client";. The built filesdist/core.jsanddist/wording/de.jsstart with the directive, then the stylesheet import, so a bundler that splits server from client code takes each as client code. The directive is written to the built entries only; the sources are unchanged.Every export is a client component. A hook called in a Server Component now fails with React's message about a client reference, not one about
useState.
Fixed
inertworks on React 18. The folded panel ofAccordionandCard, aPopoverleaving and aSplitterpane folded to nothing wrote noinertattribute under 18, which drops a boolean on an attribute it does not know, so their content stayed in the tab order. The attribute is now written in the form each major reads.
0.32.0 – Words for a snooze's reason and a plan's milestones (Oct. 2026)
Added
- Milestone count in the schedule's summary (
@umriss-ui/schedule):scheduleSummarytakes amilestonescount and ends with "2 milestones." / "2 Meilensteine." - nothing for none. - Wording for the alarm list's snooze panel (
@umriss-ui/table):snoozeDurationandsnoozeReason, in English and German ("Dauer", "Grund").
0.31.0 – One natural width of twenty characters (Oct. 2026)
Added
- Wording for the alarm list's snooze (
@umriss-ui/table):snoozeAlarm,snoozeAlarms,unsnoozeAlarm,unsnoozeAlarms, in English and German. - Wording for the schedule's milestones (
@umriss-ui/schedule):scheduleMilestone(name)- "Milestone: Release", "Meilenstein: Release".
Changed
- One natural width of 20 characters (ADR-0041):
Input,SelectandComboboxare 20 characters wide where the place asks, up from 16 - the count theMultiSelecthad; so isSlider. A row too full for it sayscharson the field that can be narrower.NumberInput,Textareaand the date pickers keep theirs. - The slider's readout takes the room of its longest value: the longer of
minandmaxas formatted, where it was a fixed six characters.
Fixed
core.cssis a third smaller: 158 kB where it was 228 kB. The shared rules every component takes (text, focus ring, caret, scrollbars) stood once per component in it; they stand once now. Nothing looks different.
0.30.0 – A card hands its height to its body (Oct. 2026)
Changed
Cardis a flex column, and its body takes the rest (ADR-0050): a card the grid stretches gives the extra height to itsCardBody- in a collapsible card, to the fold's wrapper and inner element - so that aChartwithoutheightfills it. A card that is not stretched looks as before. Whatever stands directly in a card, besideCardHeaderandCardBody, is now a flex item: its width stays, but its margins no longer collapse with its neighbours'.
0.29.0 – A calculation's empty sum (Oct. 2026)
Added
calculationNoOperandsin the wording - "no entries", German "keine Einträge": what the line of a calculation's sum with no operands says where its formula would stand (@umriss-ui/calculation0.6.0). A wording of your own that lists every entry needs it too.
0.28.0 – Grid columns of their own widths (Oct. 2026)
A grid of labels beside their values needed a gridTemplateColumns of its
own - the one place a screen had to be laid out in CSS. columns now takes a
width per column.
Added
Gridtakes a list forcolumns, one width per column: a number in pixels, or"fill"for a share of what the other columns leave -columns={[140, "fill"]}for labels beside their values, without agridTemplateColumnsof your own. A"fill"column is never widened by a long line, as the equal columns of a count are not. The list's length is the column count;minItemWidthwins over a list as over a count. The width's type is exported asGridColumnWidth. A count behaves as before.
0.27.0 – The toasts below a header, and the segmented control on its own (Oct. 2026)
Two requests from an application shell. The toasts' distance from the window's edge is a token per edge, so they can stand below a fixed header without a selector on the toast's markup. The segmented control can stand on its own - drawn inset, filling its place, with an icon before each word, as a theme switch in a menu needs - and the inset drawing is now its default (see Changed).
Changed
SegmentedControlis drawn inset by default: a sunken track without an edge, the choice a surface lifted out of it with the card shadow, the other words in the secondary ink and medium weight. It is still a control's height, with its words on a select's line in a row of fields; focus, invalid, disabled, forced colours and the keys are as before.variant="field"gives the 0.26 drawing - a field's edge, the choice filled with ink - for a row whose fields it should match box for box.
Added
--u-toast-inset-topand--u-toast-inset-bottom, the toasts' distance from the window's top and bottom edge. The default is the distance they had:--u-space-5,--u-space-4up to 600 px wide. Set on:root, the top one moves the threetop-*positions at every width - below a fixed header - and the bottom one the threebottom-*positions. A rule on the region'sdata-edgeis no longer needed, and was never safe (ADR-0045).SegmentedControltakesfill: it takes the whole width its place gives it, as a field in a column does, and shares it equally between its segments, their words centred. A word too long for its share still ends in an ellipsis. Withoutfillit is as wide as its words, as before.SegmentedOptiontakesicon, drawn before the word with a button's gap, at--u-icon-size(--u-icon-size-smatsm) and hidden from assistive technology - the word names the possibility. Where the word ends in an ellipsis, the icon stays whole.
0.26.0 – A row of fields on one line, and the segmented control (Oct. 2026)
Controls side by side in a row of fields share one line: their words stand on
the line a field's value stands on, at either size. A new control,
SegmentedControl, stands in such a row as a field itself. Three things
change height or name (see Changed).
Added
SegmentedControl: one choice out of a few short possibilities, drawn as one field with a segment for each - as tall, as edged and as rounded as a select, the chosen segment filled with ink. To the keys and the screen reader it is a radio group: one tab stop, the arrow keys move and choose, skipping disabled possibilities. It takesoptions(value,label,disabled),value(nullfor none yet) andonChangeordefaultValue,size,disabledandname; inside aFormFieldit takes the field's label, message, required and invalid state, and shows an invalid field's edge. It is as wide as its words, never wider than its place - a word that does not fit ends in an ellipsis - and fills no place by itself. Under forced colours the chosen segment takes the system's selection colours. New typesSegmentedControlPropsandSegmentedOption.
Changed
- A horizontal
RadioGroupis as tall as a control (32 px, 26 px atsm) instead of one line of its labels: the line of its labels lies on the line a field's value stands on. A layout that stacked a horizontal group tightly under something else grows by 12.5 px (9.5 px atsm). A vertical group is unchanged. - A
Checkboxor aSwitchinside aFormFieldis as tall as a control, its label on the field line, for the same reason. Outside aFormFieldboth are as compact as before. The checkbox takes that height from the size of its place (ControlSizeProvider); it still has one look and nosize. - A
RadioGroupinside aFormFieldis named by the field's label (aria-labelledby).label htmlFornames no group, and such a group had no accessible name before; a caller's ownaria-labeloraria-labelledbystill wins.
0.25.0 – Every export explained, 27 pages to configure, and a palette that keeps count (Oct. 2026)
The package explains itself where it is used: every export carries a comment,
every default a @default tag, every type a props table names can be imported,
and the demo lists all of it in an API index (ADR-0044). 27 of the demo's pages
open with a configurator, and every prop of a props table is shown by an
example or named as an exception. The command palette learns keywords and a
limit to its finds. Two things behave differently: a nested UmrissProvider
without language keeps the language around it, and the combobox family and
the pickers put a caller's aria-label and aria-describedby on the field
that has the focus (see Changed).
Added
keywordson aCommandPaletteItem: more words a candidate is found by, such as synonyms, searched but never shown. They are searched only when neither the label nor the group matched, and only for a query of three characters or more, as a contiguous run and not as a subsequence. A find through them stands behind every find in a label or a group and marks nothing. A palette without keywords finds and orders as before.maxFindsonCommandPalette: the most finds that stand at once, the best by rank, with a last line that says how many more there are (the new wording entrypaletteMoreFinds, "26 more finds", "26 weitere Funde"). The screen reader still hears the count of every find. Without it, every find stands, as before.searchedGroupon aCommandPaletteItem: the part ofgroupthat is searched, where a prefix every candidate of a set shares would otherwise let its few letters find the whole set. The heading shown is stillgroup. Without it, all ofgroupis searched, as before.- Every type a props table names can be imported:
TextTrackingandTextLeading(the text's letter spacing and line height) andAlign(where a popover lines up with its anchor) are new public types.DockPlaceis declared as its four edges itself, no longer as another name for an internal type; what it accepts is unchanged. - Every export carries a comment, the German wording's included: what a
component, a hook, a function, a constant or a type is for, and for a hook
@paramand@returnswhere its type alone does not say it. The editor shows it on hover, from the package's.d.ts. The comment onUmrissProvider'slanguagelinks the Language page instead of naming a source folder. Nothing else changes. - A prop's default stands in a
@defaulttag where its comment used to say it in words: the editor shows it on hover, and the props table in its Default column. Nothing else changes. - The demo has an API index:
/core/api/, the last entry of the sidebar, lists every export of@umriss-ui/coreand of@umriss-ui/core/wording/deunder its import path, grouped as components, hooks, functions, constants and types, alphabetical - each value with its comment and its declaration, each type with a link to its table or its definition, and the pages that use it.DEFAULT_WORDING,DEFAULT_FORMATSand their German twins link their table on the Language page. A hook or a function a page names, in its text or its import line, leads to its entry.docs/llms-full.mdends with the same index, in place of the appendix "The rest of the API". The library is unchanged. docs/llms-full.mddefines every type a props table names: a type without a table of its own (ButtonSize,Wording,Limit) stands under "Types on this page" of each page that names it, with its comment and its members or its declaration.- Every wording entry is listed, in
docs/llms-full.mdand on the Language page: each key ofWording, of the charts'ChartsWordingand ofFormats, its English beside its German and the comment its type carries. A row has an address of its own,#wording-noMatches,#format-date. - Every token listed: the text the package carries (
docs/llms-full.md) and the demo's new page Theming list every--u-…token, grouped as the stylesheet groups them, with its light and its dark value and what it is for. Below them, under#charts-tokens, the 29--uc-…tokens of@umriss-ui/charts, with the core token each one takes, linked. The tokens themselves are unchanged. - Tokens are the styling API;
data-*attributes and class names are not (ADR-0045). The--u-…tokens change only with an entry here; the attributes and class names in the components' markup are internal and may change in any version. The page Theming says so and shows the accent for the application, a token for one region and a dark region; the Installation page links there instead of explaining the tokens itself. - Every page whose examples take the keyboard says its keys: Button,
Input, Textarea, Card, Typography, Alert and Toast have a Keyboard table of
their own (Toast's names Alt+T and Escape on the deck); the other pages with
a control in their examples link the pages of those controls. Button,
Textarea, Spinner, ProgressBar, Skeleton, Alert, Toast, Stepper, Stat, Meter
and Dock have a section Accessibility after Keyboard: the role and name, what
is announced and when, the label to pass, and what forced colours and reduced
motion change. In the demo, the Markdown twins and
docs/llms-full.md; the components are unchanged. invalidhas an example on every control that takes it: Input, Textarea, NumberInput, Switch, FileInput, Select, Combobox, MultiSelect, the four pickers andTreeSearchon the TreeView page each show "With an error": a field without aFormField, marked by a live check and tied to its message byaria-describedby. Inside aFormField, itserrormarks the field invalid by itself; the lead of each example says so. In the demo anddocs/llms-full.md; the components are unchanged.- 27 pages open with a configurator: Alert, Badge, Button, Checkbox, Combobox, the four pickers, Divider, FormField, IconButton, Input, Meter, MultiSelect, NumberInput, ProgressBar, RadioGroup, Select, Skeleton, Slider, Sparkline, Stat, Switch, Tag, Textarea and Typography. The component stands beside a control for each prop that takes a value - a choice, a list, a switch, a number or a text field, bounded where the prop is - and under it the code of what stands there, every prop at its default left out. The controls, the values and the defaults come from the props table. In the demo; the components are unchanged.
- Every prop of a props table is shown, or named as an exception: new
examples for
toast.limit, a popover card at the right edge (align,offset,hideOnScroll), a modal that must be answered (closeOnBackdrop,hideClose) and a command palette over two hundred candidates (icon,searchedGroup,maxFinds); existing examples now showstepon the Splitter,weighton a Heading,sizeon a ButtonGroup,alignon a SplitButton,emptyTexton a MultiSelect, an ownplaceholderon both date-time pickers andremoveLabelon a Tag. A row a page's configurator sets names "Configurator" under "Shown in". The eight props left without an example are pass-throughs and escape hatches; the demo's list says so for each. The components are unchanged.
Changed
- An
UmrissProviderwithoutlanguagekeeps the surrounding language instead of resetting its subtree to English. A provider nested for a toast, a portal target or a density no longer undoes the wording and the formats of an outerUmrissProviderorLanguageProvider. At the root, without a provider around it, English holds as before. Combobox,MultiSelectand the four pickers takearia-label,aria-labelledbyandaria-describedbyon the field – the input or the button that has the focus – instead of the wrapper around it, asInputandSelectdo. A field without aFormFieldcan now be named and tied to its own message; inside one, a caller'saria-describedbyreplaces the field's message, as onInput.
0.24.0 – The select's own list (Oct. 2026)
Changed
- The
Selectopens its own list under a mouse, a pen and the keys (ADR-0043) - the Combobox's, in the library's look. The system's list flickered as it opened in Firefox on Windows, an unstyled select too, out of the page's reach. Under a finger the system's picker stays. The field is still the native<select>:value,defaultValue,onChange,name,required, a form's reset, therefand the<option>/<optgroup>children work as before. What a caller may notice: the list follows the page's tokens instead of the system's colours; Arrow Up and Arrow Down open it instead of changing the choice in a closed field; the select carriesaria-expandedand, while open,aria-activedescendant; a test that fireschangeon the select still works, one that clicks an option opens the list first and finds the option in thelistbox.multipleand a disabled select are unchanged.
Fixed
- 0.23.0's "a
Selectno longer flickers as it opens" fixed the focus ring fading in under Chromium's list, not the flicker of Firefox on Windows; that one goes with the list above.
0.23.0 – A multiselect that clears (Oct. 2026)
Added
clearableonMultiSelect, as onSelectandCombobox: a cross between the chips and the chevron empties the selection, without opening the panel; a chosen value whose option is disabled stays, as with "Select none". The field keeps the cross's room whether it stands or not, so a clearable multiselect is 26 px wider at the samechars, and the chips share what is left. The page MultiSelect shows it in "Filter incidents by service".
Fixed
- The
Combobox's chevron shows the pointer and opens the panel, or closes it; the focus stays in the field. It showed the text cursor of the input beneath it. - A
Selectno longer flickers as it opens. Chromium takes a click on a select for keyboard focus, and the focus ring faded in while the native list opened over it; the ring now comes at once.
0.22.0 – A tag keeps its descenders (Oct. 2026)
Added
--u-delay-stale(200 ms): the moment content being reloaded dims - a quick answer never flickers.@umriss-ui/tabledims its rows by it.
Fixed
- A
Tag's text keeps its descenders. Its line was as tall as its type, and the text clips for its ellipsis: a g, a y or the "kg" of a condition in the table's toolbar lost its tail, by a pixel in WebKit. The line is 16 px now, inside the tag's unchanged 22 px.
0.21.2 – Text that reads in a tooltip (Oct. 2026)
Fixed
TextandHeadingread inside aTooltip. The tooltip stands on the text colour, andTextset that same colour on itself: a tooltip built fromTextstood black on black, in the dark theme light on light. The tooltip's content now reads the three text colours turned round -defaultin the tooltip's own type colour,secondaryandmutedstepping back towards its ground, each above 4.5:1 in both themes. A tooltip of plain text looks as before. The Tooltip page shows it in "Show how a figure came about".
0.21.1 – A long list that stays (Oct. 2026)
Fixed
- A tick in a long
MultiSelectno longer empties the panel. The hidden input ofCheckbox,SwitchandRadioGrouphung from the nearest positioned ancestor - in a popover the panel, past the list that scrolls - and the focus a click gave it scrolled the whole panel down to where the row would stand unscrolled: the panel stayed open and white. Each row now holds its own input; nothing moves on the page.
0.21.0 – Fields that hold still (Oct. 2026)
A field's width no longer follows what it shows, and one size reaches every control in a place (ADR-0041). The new demo page Sizes shows both.
Added
charsonInput,Textarea,Select,Combobox,MultiSelect,NumberInputand the four date pickers: the width in characters of the field's own type - the room for its value, with its padding, glyph, cross and steppers added. A postcode ischars={5}, an IBANchars={27}. Given, the field is that wide in every place, and never wider than its place.ControlSizeProvider(with the typeControlSize): every control with two heights inside it takes itssize-Input,Textarea,NumberInput,Select,Combobox,MultiSelect, the date pickers,Button,IconButton,ButtonGroup,SplitButton,Switch,RadioGroup. A control's ownsizewins; a popover, dialog, drawer or tooltip opened from inside starts without it.sizeonButtonGroup, for the buttons in it that say none.- Demo: the page Sizes after FormField - a field that fills its place, sized to its value, a row that holds still while values are chosen (its widths measured live), one size for a place, narrow places.
Changed
- A field's width never follows what it shows. Its root is a block: in a
form's column, a grid's cell or a dialog it fills its place, as before. Where
the place asks it instead - a row, a toolbar, a column lined up at the start,
a cell sized by its content - it is its natural width: 16 characters for a
text field, select and combobox, 20 for a multiselect, 10 for a number, 40
for a textarea, the longest value of its formats for a date picker. Its
value, its options, its chips and its files no longer move it: a multiselect
stood 83, 96 and 133 px wide for none, one and two values in a toolbar, and
a select as wide as its longest option. In a row, a field used to take the
whole row's width; give it room with the row's own layout (
flex: 1, a grid). - A field is never wider than its place, and what does not fit ends in an
ellipsis - a range of instants scrolled a 320 px page sideways. A
FormFieldis never wider than its place either, and its hint or error wraps inside its field's width instead of widening it. Select'sselectSizeis nowsize, as on every other field, with no alias. The nativesizeof the<select>is left out.InputandTextareaalways render their wrapper, and on every wrapped field -Input,Textarea,Select,NumberInput,Checkbox,Switch,Slider,FileInput-classNameandstyleland on the wrapper, ref and every other attribute on the control.<Input style={{ width: 120 }} />sizes the field. Astylemeant for the native control itself now dresses the wrapper.Textareano longer takescols; it never reached the screen.- Every control with
sizefollows aControlSizeProvideraround it. - The combobox's panel is never narrower than 200 px, as the multiselect's is never narrower than 260.
- Under a finger a text field's root writes at the field's 16 px, so a width in characters holds there too.
0.20.0 – A small multiselect and combobox (Oct. 2026)
Added
sizeonMultiSelectandCombobox,"sm"or"md"(the default) -Input's two sizes, so that a field of one's own stands level with the search in a table toolbar. The chips keep their size.Selecthas the same underselectSize, sincesizeis the native element's own.
0.19.0 – A tree model that takes a match (Oct. 2026)
Added
NodeReader.matches- an optional predicate for the tree model. Given, a search runs and the predicate decides what matches; the search text is not read. The path rule stays: a match shows with the nodes above it, and a branch shown only for a descendant opens without its expanded state being written.@umriss-ui/table's tree rows filter by their columns and conditions through it. Without it, nothing changes.- Wording for
@umriss-ui/table's tree rows:foldBranchandunfoldBranch("Fold rows under {row}" / "Zeilen unter {row} zuklappen" - apart from the row detail's expander),levelColumn("Level" / "Ebene"),pathRow("on the path to a match" / "auf dem Weg zu einem Treffer") andfooterTopLevel("Top level only" / "Nur oberste Ebene"). An application that implements the wholeWordingtype adds the five keys; a partial wording passed to the provider needs nothing.
0.18.0 – Buttons a step tighter (Sep. 2026)
Added
--u-button-padding(10px) and--u-button-padding-sm(6px), the room left and right of a button's label atmdandsm.
Changed
- Buttons are tighter still: 10px of room a side at
md(12px in 0.17.0), 6px atsm(8px), chosen on a rendered comparison of three steps. The heights, the type and the icon size stay; anIconButtonstays square, and aSplitButton's menu trigger keeps 8px a side, so that it stays a 24px target. - The date pickers' "Today" and "Clear" and the MultiSelect's "All", "None" and "Invert" hang out by the new padding, so that their words still stand on the edge they align with.
0.17.0 – Tighter buttons, and a button for an icon (Sep. 2026)
Added
IconButton, a button that shows only an icon: square at the control heights (32px, 26px atsm), with a requiredaria-labelthat is its accessible name and always shows as its tooltip. The icon is the children - an SVG, a glyph of the set or an icon font's element - and the button sets its size. Everything else is aButton's: the sizes,loading(the spinner takes the icon's place),disabled, and the variants, withplainas the default. It anchors aMenuas its trigger. It takes noaria-labelledbyand notitle, which would part the name from the tooltip. New typeIconButtonProps.plain, a fifth variant ofButtonandIconButton: no ground, the secondary text colour, the sunken surface and the full text colour under the pointer.ghoststays quiet in the accent.--u-icon-size(14px) and--u-icon-size-sm(12px), the size of the icon in a button atmdandsm.
Changed
- Buttons are tighter: 12px of room a side at
md(was 16px), 8px atsm(was 12px). The heights stay. - A
Buttonsizes the SVG among its children: a directsvgchild of its label stands at--u-icon-size,--u-icon-size-smatsm- the size anIconButtongives its icon. Awidthandheighton that SVG no longer apply. The loading spinner takes the same size. - A
Tooltipopens for keyboard focus only, not for the focus a click leaves behind: after a click the tip came back over what the click had started, and a dialog opened by the pointer showed its close button's tip. - A
Tooltipno longer describes its trigger with the words that name it: a tip whose content equals the trigger'saria-labelsets noaria-describedby, so the dock's tools, among others, are announced once. TooltipandMenukeep their trigger's ref beside their own, instead of replacing it.- The Modal's close button and the calendar's month paging are
IconButtons: 26px squares (they were 28px and 24px) with their name as a tooltip.
0.16.0 – A toast of its own, and room on a phone (Sep. 2026)
Added
- A toast takes one
action({ label, onClick }) - "Undo", "Show". Pressing it runsonClickand closes the toast. More than one action is a dialog. onClose(reason)on a toast, called once when it goes:"timeout","dismiss"(closed,dismiss(id), or pushed out by a newer toast) or"action". New typesToastActionandToastCloseReason.update(id, options)fromuseToast, and the toneloading: a spinner in the glyph's place, no close button, no timer.updateturns it into its outcome in place; a new tone or a newdurationstarts the time afresh.toast.positionin theUmrissProvider(ToastPosition:top-start,top-center,top-end,bottom-start,bottom-center,bottom-end; defaultbottom-end) andtoast.limit(default 3).- Alt+T moves the focus into the toasts, Escape gives it back. The
toasts' region carries the name of the new wording entry
toastRegionwhile a toast stands; the deck's count is named bytoastDeckCount(English and German).
Changed
- The toast has a shape of its own. A fixed width of 360px (on a phone the window's width less 16px), a tone rail at its edge, and the glyph as the first character of the title, 16px, with no disc behind it - a wrapped title returns to the left edge. Danger, warning and neutral draw a ring, a triangle and a ring around their marks.
- Several toasts stand as a deck: the newest in front with a count ("1 / 3"), two edges behind it. The deck opens under the pointer, with the keyboard focus inside it, on its count, or with Alt+T; while it is open, every countdown stands still - before, only the toast under the pointer held, and the keyboard focus held none.
- At most three toasts stand at once. The oldest gives way to a new one,
as if closed (
toast.limitsets another number). Before, they stacked up without an end. - A horizontal
Steppermeasures its room. Too narrow for each label on one line beside its marker, the labels stand beneath the markers; narrower still - five steps on a phone - the steps take a column, asorientation="vertical"does. The list says which asdata-fit(row,stacked,column). Before, the labels ran into each other and the lines struck them through. CardHeader's actions wrap beneath the title once the title would get less than 8rem beside them. Without actions and without folding, the header renders no empty actions area.CheckboxandSwitchstand at the label's first line, not at the middle of a label that wraps.
Fixed
- The pickers' clearing cross no longer covers the value. On a touch screen it stands for good, and in a field as narrow as its value it lay on the last digits ("18.03.2026").
FileInput's file list stays inside the zone: a long name ends in an ellipsis, and its size and cross stay in sight.- A clearable
Inputoftype="search"shows one cross, not the browser's beside its own; a text wider than anyInputends in an ellipsis. Textarea's count stands clear of the resize grip.
0.15.0 – A step for dense surfaces (Sep. 2026)
Added
--u-control-height-xs(18px), the small control on a dense surface.@umriss-ui/tablegives it to the fields and buttons inside a table atdensity="compact", under a fine pointer. Like every token it can be overridden at:root.
Fixed
NumberInput size="sm"'s steppers are never taller than the field. At a field shorter than 21px they stood out above and below it.
0.14.1 – Found by search (Sep. 2026)
Fixed
- The package's description and keywords say what it is in the words a
search uses, and the demo it links to addresses every page by a path
(
…/core/<page>/) instead of a hash, with the page's text readable before the demo starts. Old#/<page>links still land. Nothing in the package's code changed.
0.14.0 – Words for a table's rows (Sep. 2026)
Added
- Seven wording keys for
@umriss-ui/table's row editing (ADR-0036):newRow("New row" / "Neue Zeile"),saveRow,discardRow,deleteRow,deleteRowAsk("Delete?" / "Löschen?"),keepRowandsaveOrDiscardFirst("Save or discard this row first" / "Erst diese Zeile speichern oder verwerfen"). An application that implements the wholeWordingtype adds them; a partial wording passed to the provider needs nothing.
0.13.1 – A round end point in Safari (Sep. 2026)
Fixed
- The end point of a
Sparklinewithwidth="fill"is round in every browser. It was a round cap inside the stretched drawing, which Chromium drew round and Safari stretched with the line - a dot crossed by a flat ellipse. It is now drawn on its own, 5 px, at the end of the line, and keeps its ground under forced colours.
0.13.0 – A tile's history across the tile (Sep. 2026)
Added
Sparklinetakeswidth="fill": it is as wide as its container and as high asheightor a stylesheet says, and the drawing follows its box. The line keeps its 1.5 px stroke and the end point stays round, however far it is stretched. The default stays 96 px - a row width, for cells.
Changed
- A
Stat's history fills the tile. It ran at a fixed 96 × 28 px, and a tile in a wide grid kept its line in the first third and the rest empty. Now it runs across the tile beneath the figures, 32 px high; in a tile of 380 px and more it stands beside the figures and takes their height. The tile decides by its own width (a container query), not by the window's. - The freshness line stands with the figures, above the history, not beneath it: it speaks about the value.
0.12.1 – A popover's own surface (Sep. 2026)
Changed
Popoverbrings the overlay's surface: the ground, the medium corners, the overlay shadow and the edge forced colours draw, on the panel itself. A popover used directly - with its content drawing the surface - lost its shadow and its round corners to the panel's scroll clip and stood flat on the page. Padding and inner layout stay the caller's; aclassNamerestates the surface where one needs another, asMenu, the pickers and the table's panels do. A caller that drew the surface in its content can drop that.
0.12.0 – Popovers that stay by their trigger (Sep. 2026)
Added
- Wording for the table's icon keys' tooltips:
columnForwardHint,columnBackwardHint,pinToStartHint,pinToEndHint,unpinHint("Move forward", "Move backward", "Pin to start", "Pin to end", "Unpin"; German "Nach vorn", "Nach hinten", "Am Anfang fixieren", "Am Ende fixieren", "Fixierung lösen") andfilterHint("Filter", German "Filtern"). Short, without the column: the keys' accessible names keep it. An application that implements the wholeWordingtype adds the six keys.
Changed
- A popover flips across as it flips along. Where the edge its caller asks
for (
align) would push the panel out of the window and the other edge would not, it lines up with the other one; where neither fits, it is centred under its trigger if that fits; only then is it pushed against the window's edge. It grows out of the corner it really stands at. Before, a panel that did not fit was pushed against the edge and lined up with nothing.Menu, the date pickers,Combobox,MultiSelect,ContextMenuand every other panel onPopoverfollow. - A popover that fits on neither side keeps its trigger in view. It takes the side with more room, is cut to it and scrolls in itself - where that side has at least 240 px. Below that it is pulled into the window as before, over its trigger if need be. Before, every such panel was pulled over its trigger: a column menu at a laptop's height hid the button that opened it.
- A popover keeps the window's 8 px margin when it only just fits. A panel that fitted to the pixel stood flush against the window's edge.
- A tooltip gives way to its trigger's own panel. While the trigger says
aria-expanded="true", the tooltip does not show, and a press hides it. A tooltip over a filter key stood over the filter's panel.
Fixed
- A tall popover no longer sticks out of the window at the bottom. It was measured while its entrance animation still scaled it down, and placed as if it were four per cent smaller.
- A scroll inside a popover no longer throws it back to its top. It was taken for a page scroll and the panel measured anew.
DateRangePickerandDateTimeRangePickeron a narrow window. The presets widened the panel to the window while the months below stood one month wide, and the time row ofDateTimeRangePickerpressed "From" and "To" into each other; the presets now wrap within the months, and below 600 px the two times stand one under the other.
0.11.0 – Data-dense applications (Sep. 2026)
Added
- Wording for the schedule's blocked time:
scheduleBlockedTime("Blocked time", German "Sperrzeit") on the ghost of a drag held back from it, andscheduleInBlockedTime(label)("In blocked time (Leave)", German "In der Sperrzeit (Urlaub)") in the tooltip and the readout.scheduleSummarygets the fieldblocked, the subtasks in blocked time, and says it only where it is not zero. An application that implements the wholeWordingtype adds the two keys.
Changed
The alarm wording keys follow
@umriss-ui/table's new names (ADR-0035), with no deprecated aliases. A partial wording passed to the provider names the new keys; an application that implements the wholeWordingtype renames them.Before Now lifecycleStandingUnacknowledged,lifecycleStandingAcknowledgedlifecycleActiveUnacknowledged,lifecycleActiveAcknowledgedlifecycleClearedUnacknowledged,lifecycleClearedAcknowledgedlifecycleResolvedUnacknowledged,lifecycleResolvedAcknowledgedstandingUnacknowledged(count)activeUnacknowledged(count)availabilityShelved(until, by),availabilityShelvedShortavailabilitySnoozed(until, by),availabilitySnoozedShortavailabilitySuppressedByDesignavailabilitySuppressedavailabilityOutOfServiceavailabilityDisabledhiddenFromOperation(count)hiddenAlarms(count)The default texts change with them. English: "Active, unacknowledged", "Resolved, acknowledged", "3 active alarms, unacknowledged", "Snoozed until 12:00 by M. Keller", "Suppressed", "Disabled", "Hidden: 3". German (
@umriss-ui/core/wording/de): "Aktiv, unquittiert", "Behoben, quittiert", "3 aktive Meldungen, unquittiert", "Unterdrückt", "Deaktiviert", "Ausgeblendet: 3"; "Zurückgestellt" stays.The schedule's wording keys follow its renamed terms (ADR-0035): a transport is a dependency, setup and teardown are lead-in and lead-out, and a late transport is a violated dependency. The keys are renamed in one cut, with no alias; an application that hands in a whole
Wordingobject of its own renames its entries. The English texts read "Lead-in", "Lead-out", "Dependency" and "Violated dependency", the German ones "Vorlauf", "Nachlauf", "Abhängigkeit" and "Abhängigkeit verletzt"; the key help follows a dependency instead of a transport.Before Now scheduleSetupscheduleLeadInscheduleTeardownscheduleLeadOutscheduleTransportscheduleDependencyscheduleLateTransportscheduleViolatedDependencyscheduleLateByscheduleViolatedByscheduleSummary's fieldlateviolated
Fixed
useVirtualadopts a row height half a pixel off the expected one. It ignored anything up to 0.5 px, so rows of 37.5 px under arowHeightof 37 left the scrollbar of twenty thousand rows ten thousand pixels short.- A disabled
FileInput's zone says it is disabled (aria-disabled): its hint and key text belong to the inactive control, and a screen reader and a contrast check now read them as such.
0.10.0 – Everything the comparison asked for (Sep. 2026)
Added
--u-shadow-sticky: the colour a sticking part's shadow starts from -@umriss-ui/table's pinned blocks and its stuck group header. Light as before; dark is black at 50 % instead of the text colour at 9 %, which read as a glow on the dark ground.- The Windows contrast mode (
forced-colors: active, forced-colors 01, 02). Forced colours drop every box-shadow and repaint every ground, and the library draws its rings, edges and depth as box-shadows: a focused control lost its ring, a button became a word, a field a line of text, a menu text floating over the page. Every ring now carriesoutline: 2px solid transparentand every edge or depthoutline: 1px solid transparent- both invisible until forced colours paint them in a system colour - and the states that were a ground alone take a system colour: the tab's underline, the switch that is on, the progress bar's and the meter's fill, the checkbox's mixed dash, the radio's dot and the divider's line; the list cursor ofComboboxandCommandPalette, the activeTreeViewnode and the chosen days, range and preset of the date pickers an outline in the selection colour. Buttons, badges, tags andMultiSelect's chips keep an edge. Nothing changes outside forced colours. docs/llms-full.md, the package's documentation as one Markdown file for a coding agent, in the npm package and pinned to its version: every demo page with its import line, its examples' source, its props tables and why it is built as it is, and the declaration of every other export. The same text stands online as https://romanhaendler.github.io/umriss-ui/core/llms-full.txt, with an index of the pages beside it (llms.txt).- Every component takes a
ref, aclassName, astyleand the rest of the DOM props at the element a caller lays out - a tooltip on aStat, a measuredCard, adata-testidanywhere. New forBadge,Card,CardHeader,CardBody,Combobox,CommandPalette,ConfirmDialog,EmptyState,FormField,Meter,Modal,ModalHeader,ModalBody,ModalFooter,MenuItem,MenuSeparator,MultiSelect,Skeleton,Sparkline,Spinner,Stat,Tabs,TabList,Tab,TabPanel,TreeView,TreeSearchand the four pickers. The<dialog>takes it forModal,ConfirmDialogandCommandPalette; the field's wrapper for the pickers,ComboboxandMultiSelect(the id fromFormFieldstays on the control inside); therole="tree"list forTreeView. All of them areforwardRefcomponents, so the ref also arrives under React 18. Tabswork uncontrolled:defaultValuenames the first tab, and the tabs switch on their own;valueandonChangeare both optional now. Without either, every tab is a tab stop until one is chosen.Cardcan be controlled:collapsedfolds it from outside, andonCollapsedChangereports the header's button - controlled, the card folds oncecollapsedfollows; besidedefaultCollapsedit is a message.Switch: on or off, taking effect at once - a native checkbox underrole="switch"with its label beside it,checked/defaultChecked, sizessmandmd,invalid(set byFormFieldwith an error). The class goes to the label, ref and rest to the input, as forCheckbox.Slider: one value on the native range input, drawn with tokens -value/defaultValuewithonChange(value),min,max,step,marks(a number, or{ value, label }),formatfor the mono readout (showValue) andaria-valuetext. Arrows by a step, PageUp/PageDown by a tenth of the range, Home/End to the bounds, the same on every engine.Drawer: theModal's dialog entering from an edge,side="right" | "left", withModalHeader,ModalBodyandModalFooterinside; its width is the new token--u-drawer-width(400px).ProgressBar: how far a task has come,role="progressbar"- determinate withvaluefrom 0 to 1, indeterminate without;valueTextfor a count,showLabelfor the percentage. No tone: progress is no verdict. New token--u-duration-sweepfor the indeterminate bar.Accordion/AccordionItem: sections behind headers that are buttons witharia-expandedinside a heading (headingLevel),type="single" | "multiple",value/defaultValueas a list withonChange, arrow keys, Home and End between the headers,disableditems.Breadcrumb:itemsfrom the root to the current page, the lastaria-current="page", each a link (href), a button (onSelect) or both - withonSelectthe caller routes. Where the trail does not fit, its middle levels fold into a menu, measured from the trail's own widths.Combobox,MultiSelectandCommandPalettesay what VoiceOver leaves out of a list, through one polite live region the library shares: the number of options on opening and on every letter of the search ("4 options", the empty text where none is left), the option the arrow keys moved onto with its state ("Gamma, selected", "Beta, unavailable"; the palette's find with its group), and in the multi-select what Enter in the search, a chip or Backspace added or removed. It speaks once the keys rest for 150 ms, the charts' readout pause, and inside a<dialog>from the dialog's own region.Splitter: two panes and the line between them, the APG window splitter -orientation="horizontal"side by side or"vertical"stacked,value/defaultValueas the first pane's share in per cent withonChange(value),min,maxandstep. The line is a focusablerole="separator"(separatorLabelnames it after the first pane): the pointer drags it, the arrows of its axis move it, Home and End take it to the bounds, Enter collapses the first pane tominand restores it.Stepper: where a procedure stands -steps(label,description,failed) and the indexcurrent, as an ordered list in a row or a column (orientation). The current step isaria-current="step"; every other step's state - done, upcoming, failed - is a word beside its label for a screen reader and a tick, number or cross in its marker. It has no keys: moving on is the caller's.FileInput: the native file input behind a key, inside a zone that takes a drop.acceptandmultiplehold for a drop as they hold in the dialog (a refused file is named beneath the list), the chosen files stand listed with their size and a cross each,valuewithonChange(files)or uncontrolled,invalidfromFormField. What a drop or a removal leaves is written back into the input, so a form sends what the list shows. The class goes to the zone, ref and rest to the input. Nothing is uploaded.
Changed
scheduleKeyHelpnames T beside the brackets, in English and German:@umriss-ui/scheduletakestand Shift+T as equals of]and[, since a German keyboard reaches the brackets only with AltGr. A wording of one's own keeps its text until it says so too.The final polish of the new components and of forced colours (core-foundations 07, core-layout-extras 04, forced-colors 04). The indeterminate
ProgressBarrests with its segment in the middle of the track, its ends fading, and sweeps from there - with animations off it no longer reads as a share done. The key of aBreadcrumb's folded levels is as tall and as wide as a small key and underlines its dots; the trail grows to that height when it folds. An invalidSliderdraws its track with the fields' danger edge. TheSplitter's line carries the grip glyph in its middle, quiet at rest and darker under the pointer. Under forced colours the primaryButtonhas an edge of two pixels (the second inside, so it stands as large as its neighbours) and aBadgekeeps its dot in the text colour.A new key in
Wording: a shelf as one word (for@umriss-ui/table's alarm list, whose availability is now a badge), in English and German:availabilitySnoozedShort("Shelved", "Zurückgestellt"). The sentenceavailabilitySnoozedstays - in the badge's tooltip and in what is spoken. An application that implements the wholeWordingtype itself no longer compiles until it adds it.Two new keys in
Wording: a table over a server (for@umriss-ui/table's manual mode), in English and German:selectAllOnPage("Select all on this page") andexportPageLabel("Export page"). A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds these two.pageOfPagestakes the numbers formatted as well: a third, optional argument{ page, total }carries them in the provider's formats, and the English and German wording write those - "Page 1 of 100,000", "Seite 1 von 100.000", since a table over a server has that many pages. Below a thousand pages nothing changes, and a wording of one's own that reads two arguments still compiles.Eleven new keys in
Wording, forSplitter,StepperandFileInput, in English and German:splitter(the separator's name where the caller gives none),stepDone,stepUpcomingandstepFailed(a step's state beside its label),chooseFileandchooseFiles(the key),dropFileanddropFiles(the words beside it),removeFile(name)andfileNotAccepted(name). A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds these eleven.Four new keys in
Wording: what the lists announce, in English and German:optionCount(count),optionActive(label, { selected, disabled, group }),optionAdded(label)andoptionRemoved(label). A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds these four.CommandPaletteno longer renders arole="status"element of its own. The count of finds is spoken through the shared region, which the palette's<dialog>now holds as its last child once something was said; it is also said on opening whenrestingItemsstand, and for a query that finds nothing (paletteNoFinds).A new key in
Wording:editCell(column, row), the name of a cell's editor in@umriss-ui/table's grid mode ("Edit Setpoint: TIC-101"), in English and German. A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds it.Three new keys in
Wording: the column menu's pin keys (for@umriss-ui/table's pinned columns), in English and German:pinColumnToStart(column),pinColumnToEnd(column)andunpinColumn(column). A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds these three.Five new keys in
Wording: the wording of an alarm's availability (for@umriss-ui/table's alarm model and list), in English and German:availabilitySnoozed(until, by),availabilitySuppressed,availabilityDisabled,hiddenAlarms(count)andcolumnAvailability. A partial wording passed to the provider is merged as before; an application that implements the wholeWordingtype itself no longer compiles until it adds these five.Three more keys in
Wording, for the new components, in English and German:progress(aProgressBar's name where the caller gives none),breadcrumb(the landmark's name) andbreadcrumbFolded(the key of the folded levels). The same holds as above: a partial wording merges, a wholeWordingof an application's own needs the three.What
...restmay not override.Statkeeps its role and the name it has read,Meterits role andaria-value*,Spinner,TreeView,TabList,TabPanelandMenuItemtheir role,Tabits role, ids and tab stop, even where a caller passes the same attribute. A caller'saria-labelstill names aMeter, aSpinner, aModalor theCommandPalette.A caller's handler runs first and can prevent the component's own:
onCancelandonMouseDownofModalandCommandPalette(apreventDefaultkeeps the window standing),onKeyDownofComboboxandCommandPalette(heard in the capture phase, before the field inside),onClickandonKeyDownofMultiSelect's field, andonClickofTabandMenuItem, which ran first before but could not prevent.
Fixed
- A drawer from the right no longer shoots in too far and jumps back.
The browser gives a modal dialog
overflow: auto; the focus landing in the sheet while it still stood off-screen scrolled the dialog towards it. The drawer's dialog is clipped now. - A drawer from the left fades as it leaves; its entrance rule outweighed the exit and it vanished at once.
- "1 hr", not "1 hrs", in the English
hoursShort.
0.9.2 – Today's dot, one chevron (Sep. 2026)
Changed
- Today is marked by a dot beneath its figure, no longer by an outline. The outline read like the keyboard focus beside it; the focus is now the only ring in the calendar. On a chosen day the dot takes the ink's type colour. Applies to all four pickers.
- Select, Combobox and MultiSelect draw their chevron with
AngleGlyph, turned down: the one stroke width of every glyph, a shade smaller and rounder than the CSS angle it replaces.
0.9.1 – Depth work on the pickers and the select family (Sep. 2026)
Fixed
- A compact date field's placeholder is compact.
size="sm"shrank the value but left the placeholder at the comfortable size; it now matches anInputof sizesm. Applies to all four pickers. - A
Selectshowing its placeholder option (value="") reads muted, like theCombobox's placeholder and the pickers'. It stood in full ink. - The pickers' ghost keys at the panel's edge align with the calendar: "Today", "Now" and "Clear" stood a key's inset in from the grid, the hairline and the TIME label above them.
0.9.0 – A token for figures (Sep. 2026)
Added
--u-tracking-figures(-0.01em), the tracking of figures set in Geist Mono. It carries exactly the value that stood written out in the number field, the input's numeric mode, the date picker's field, the multi-select's count and chip, and the data-viz figures; nothing moves.
Target environment: applications that render in the browser
The library is aimed at client-side rendered applications. The markers a
server-rendering framework expects at the client boundary ("use client" at the
head of the modules) are missing on purpose. That is a decision, not an
omission.
The reason: practically every component of the package holds state, hangs on focus, pointer events or layout measurement, and on the server would be nothing but a shell. The annotations would therefore enable nothing that is missing today — they would open a guarantee (this package runs under server rendering) that nobody checks and that breaks silently at the first extension. Server-side rendering is not on the plan; should it ever arrive, it will be decided as a delivery of its own and noted here.
For the caller that means: in an application with server rendering, the
components of this package belong behind a client boundary of their own — a
module on the caller's side that carries "use client" and imports from there.
0.8.0 – Motion, one stroke, one canon of states (Sep. 2026)
Added
--u-text-mono(0.75rem), the size of figures set in Geist Mono inside a control or a cell, and--u-leading-none(1), the line height of a box as tall as its type. Both carry exactly the value that stood written out in eight and six places before; nothing moves.A motion vocabulary in
tokens.css, naming the distinctions the library makes:--u-ease-exit(an exit that starts at once),--u-duration-presswith--u-transition-press(the press point),--u-transition-path(something turns or slides),--u-duration-drawand--u-delay-draw(the checkbox's tick),--u-duration-exit-fast(a panel or tooltip leaves), and for continuous processes--u-duration-spin,--u-duration-shimmer,--u-ease-steadyand--u-ease-swell. Every motion in the library's stylesheets now reads one of them.CalendarGlyph(withrange) andClockGlyph, the date and time pickers' characters, in the shared set besideCrossGlyphand the others.Three pressed surfaces:
--u-color-surface-pressed,--u-color-accent-subtle-pressedand--u-color-danger-subtle-pressed, the step below the sunken, subtle-accent and subtle-danger hovers. Each holds 4.5:1 under the type that stands on it, in both themes.
Changed
--u-transitionruns on--u-ease-out. The collective token for hover and colour carried the CSS keywordease-out; it now carries the library's own curve (cubic-bezier(0.22, 1, 0.36, 1)), which until now reached only the few transitions that named it. Every hover, focus-edge and colour change in core, table, schedule and calculation starts more decisively and lands more softly; its duration (120 ms) is unchanged. An application that overrides--u-transitionis not affected.Exits leave instead of retracing.
Modalenters by scale and opacity from its centre (the 8 px rise is gone) and leaves by fading where it stands, on--u-ease-exitinstead ofease-in; its backdrop follows the same curves. The leavingToastglides out on--u-ease-exitas well.Press points take 80 ms. The pressed glyphs of
Alert,Tag,NumberInputand the date picker's time stepper ran on the 120 ms of a hover; they now take the button's press duration.Overlays unfold from their trigger. Every panel on
Popover-Menu,ContextMenu, the lists ofComboboxandMultiSelect, the panels of the four date pickers - enters by scale and opacity from its motion origin, the corner facing its trigger (a panel below a start-aligned trigger grows from its top left; a flipped one from its bottom). It used to slide 6 px. On closing it stays for--u-duration-exit-fast(100 ms), inert, and fades where it stands; it no longer disappears in the same frame.Tooltipfollows the same pattern from the edge facing its trigger, and eachToastgrows out of the bottom right corner it stands in. Under reduced motion all of them appear and go at once.Selectkeeps the browser's own list.Every glyph draws one stroke width at one nominal size – 1.4 per 10 units, so the rendered stroke follows the size a glyph is set at. The
Alert,ModalandToastclose crosses, theTagandMultiSelectremove crosses are nowCrossGlyph: the Modal's cross is slightly heavier, the Tag's and the chip's slightly lighter. The calendar's paging arrows, theButtonGrouparrow and theComboboxtick go from 1.5 and 1.8 to 1.4. The date pickers' calendar leaf and clock are drawn anew at nominal size 10 and set at 12 pixels instead of 13; the range picker's leaf shows a bar instead of an arrow. TheToasttone symbols keep their look, drawn in the nominal box, their dots as strokes. Held by a check (docs/glyphs.md).One canon of interaction states. Hover changes the surface, a press changes it more strongly, focus is
--u-focus-ringand nothing else, and disabled dims to half and reacts to nothing. What that changes:- Pressed: every quiet key now sinks while it is pressed -
Buttonsecondaryandghost, the close keys ofModal,ToastandAlert, the remove key ofTag, the clear keys of the fields, the steppers, the date picker's pages, days and presets, the items ofMenu, the options ofComboboxandMultiSelect, the rows ofTreeViewandCommandPalette, the tools ofDock, the chips and the counter ofMultiSelect, the collapse key ofCard, and the empty boxes ofCheckboxandRadioGroup. Before, only the primary and danger buttons darkened. - Focus: a
Menuitem reached by the keyboard carries the ring instead of the hover surface, and so does aMultiSelectchip, which showed its focus in the danger surface. - Disabled: the fields (
Input,Textarea,Select,Combobox,NumberInput,MultiSelect, the date pickers' trigger and time fields),Checkbox, aTabstab, aMenuitem, aComboboxoption and aDocktool dim to half opacity, asButtonandRadioGroupalways did, instead of being repainted in a sunken ground and muted type. A disabledDocktool shows the not-allowed cursor. Checkboxhas a hover edge on its empty box, asRadioGrouphas.NumberInput's steppers settle on the glyph (0.8) like the date picker's, instead of scaling the whole key to 0.9.
- Pressed: every quiet key now sinks while it is pressed -
Fixed
- Disabled elements reacted underneath. A disabled
Tabstab darkened under the pointer; a disabled time stepper of the date pickers came back to full presence and its hover surface when the pointer entered its group; the × of a disabledMultiSelectchip appeared under the pointer; and the pointer movedCombobox's cursor onto a disabled option (the arrow keys still reach it). - A hover took the focus ring away. On a focused
Buttonsecondary,CheckboxorRadioGroupthe hover edge replaced the ring while the pointer rested on it; on the date pickers the chosen day, the ends of a range and the preview's end dropped the ring the same way.
0.7.0 – A text token for danger (Sep. 2026)
Added
--u-color-danger-text, the danger tone as type, beside--u-color-dangeras surface — the split the accent has long had with--u-color-accent-text. It holds 4.5:1 on the subtle danger surface, the surface and the page ground in both themes.
Changed
- Danger text reads
--u-color-danger-text. In the dark theme it is lighter (#d86f66instead of#d0655c); the surface value measured only 4.40:1 on--u-color-danger-subtle. It applies where danger is type: the danger tone ofBadge,Tagand theAlerttitle, theFormFielderror and required mark, the exceededTextareacount, the dangerMenuitem, the removingMultiSelectchip, the alarm verdict and the lost freshness ofStat. Surfaces, edges, focus rings and theToasticon keep--u-color-danger. The light theme is unchanged. A caller who drew danger text with--u-color-dangershould switch to the new token.
0.6.0 – The wording of the grouping (Sep. 2026)
Added
- The wording of the table's grouping and aggregates, English and German:
the footer's minimum, maximum, range, count, distinct values and an aggregate
of one's own; the grouping section and its entry per column, "Grouped by",
removing the grouping or a level, fold all and unfold all, "No value",
"continued", the fold's and the group box's names, and the calendar week. A
caller who builds a complete
Wordingof their own has nineteen entries to add.
0.5.0 – The wording of the calculation (Sep. 2026)
Added
- The wording of
@umriss-ui/calculation, English and German: the four operators as symbols and as words,calculationEquals,calculationApproximatelyand the approximation note,calculationPercent, the two reasons for an absent quantity (calculationMissing,calculationDivisionByZero), the target phrases, the worst-verdict marker,calculationSource, the disclosure labels andcalculationOperandCount. A caller who builds a completeWordingof their own has twenty-two entries to add.
0.4.0 – One focus, and fields on the iPhone (Sep. 2026)
Changed
- Focus is a crisp two-pixel accent edge, on every component.
--u-focus-ringwas a soft 4px glow at 20 %, and fields drew an accent edge inside it; now it is0 0 0 2pxin the accent, for fields, buttons, checkboxes, radios, tags, rows and links alike. An invalid field keeps its danger colour while focused (new token--u-focus-ring-danger). A caller who set--u-focus-ringkeeps their value. - On a touch screen, fields write at 16px (
pointer: coarse). Below that, Safari on iOS zoomed the page into the field and left it zoomed. - A loading button is no longer faded to half. It stays locked, but at full strength, so its spinner is visible - in the dark theme it all but vanished.
Fixed
- Popovers stand in the visible part of the window. They measured it with
innerWidth/innerHeight; on an iPhone with the page zoomed and the keyboard up, a combobox's list stood above the field and cut off at the left. They now readvisualViewportand follow it as the keyboard comes and goes. The same measure places the tooltip. - No browser outline beside the focus edge. Input, Combobox, Select and the
DatePicker trigger lacked
outline: none; Safari drew its blue ring outside the accent edge. - TreeView's loading spinner takes the secondary text colour instead of the muted one, which was barely visible on the dark surface.
0.3.2 – Phones, touch and a review (Sep. 2026)
Changed
- A handler a caller hands in runs beside the component's own instead of
replacing it.
Tabtook anonClick,MenuItemanonClick,TabList,TagandRadioGroupanonKeyDown,NumberInputanonFocus,onBlurandonKeyDown, andTreeViewanonFocus,onBlurandonScrollthrough their spread props - and with them the component's own work: a tab that did not switch, an entry that left its menu open, a tag Delete did not remove, a radio group the arrows did not move, a tree whose focus stopped following the active node.NumberInputwas the sharpest case: a form library'sonBlurtook the commit on leaving, and the field then counted as focused for good and took no value from outside any more. The caller's handler now runs first; where a key handler callspreventDefault(), the component leaves that key alone. - Paging a calendar takes the active day along. The arrow buttons of all four pickers move the active day - which carries the grid's one tab stop - by the same month, the 31st landing on the last day of a shorter month. Left behind in the month paged away from, it took the stop with it, and Tab went past the grid. The focus stays on the arrow that paged.
Fixed
- A popover never leaves the window. Its panel is capped at the window with
eight pixels of air (
max-width: calc(100vw - 16px),max-height: calc(100dvh - 16px)) and scrolls in itself beyond that; a panel that fits neither above nor below its trigger is pulled into the window, over the trigger if need be, instead of hanging out of it. - A popover measures its panel exactly. It read the rounded
offsetWidth/offsetHeight, and half a pixel decided whether a panel at the window's edge fitted or stuck out. - The range pickers fit a phone. Below 760px the presets of
DateRangePickerandDateTimeRangePickerstand above the months as a row; below 600px the two months stand one below the other. - The × of
Selectand of the pickers stands on a touch screen. It faded in on hover, which a finger has not; on(hover: none)it now stands whenever there is a value. Sparklinekeeps its width in a flex row. It shrank with the row; it is nowflex: none.- Escape closes the popover and nothing more. A menu, picker or select panel
inside a
Modaltook the modal down with it, because the same key is the dialog's close request. The popover now keeps the key to itself. - The first opening moves the focus as well.
MenuandContextMenuput the focus on their first entry,MultiSelectinto its search and the pickers onto the active day - but not on the first opening of each: the panel was hidden (visibility: hidden) until its first measurement, and a hidden element takes no focus. It is now transparent for that pass, which is never painted.MenuandMultiSelectalso moved their focus in an effect that ran before the panel stood; they now do it the moment it arrives, asContextMenualready did. - Tab out of an open menu goes on from the trigger. The panel is portalled
to the end of the page, and a Tab from an entry went to the top of the page.
MenuandContextMenunow give the focus back first, and the Tab moves on from there. Modalis named by its header.ModalHeader'stitlepromised to name the window for the screen reader; a<dialog>takes no name from its content, so it said only "dialog". The dialog now points at the heading witharia-labelledby- anaria-labelledbyoraria-labelof the caller's still wins.- An uncontrolled
RadioGroupis entered at its choice. It wrote a tab stop on the initial option and, since no render follows a choice, kept it there: Tab came back to the initial option rather than the chosen one. Uncontrolled, it now leaves the tab stop to the browser's own rule for radio groups. Tooltipadds to its target's description. While it stood, it replaced the target's ownaria-describedby, and a field's hint went silent.- An input method's Enter chooses nothing. In
CommandPalette,Comboboxand the search ofMultiSelect, the Enter that ends a composition (Japanese, Chinese, Korean) was taken as a choice. NumberInputwrites its first value in the notation of the formats. The first render used the default notation and the next one corrected it, so an application withGERMAN_FORMATSsaw1,234.5for a frame. Its description still spoke of German notation as well; it names the formats now.- The line under the chosen tab follows a late font. It was measured before the font had loaded and stood a pixel or two beside its tab until the window changed size; it is measured again once the fonts are ready.
- A
Dockunmounted in the middle of a drag ends the drag. Its Escape listener on the window outlived it, and a later Escape reported a place for a dock that no longer existed. Alert'sdismissLabelnames its real default - the wording's "Close message", not "Close".
0.3.1 – The README catches up (Sep. 2026)
Nothing in the code changed. The README that shipped with 0.3.0 still said
the formats default to German notation, and so did the line on NumberInput;
both describe ADR-0024 now, with the import that takes GERMAN_FORMATS beside
GERMAN_WORDING.
The 0.3.0 entry below listed formatsFor(locale) as added. It is not
exported — it builds DEFAULT_FORMATS and GERMAN_FORMATS inside the package
and nothing else — and the entry is gone.
0.3.0 – English formats, and the context menu (Sep. 2026)
Changed
- The formats are English.
DEFAULT_FORMATSwritesen-GBon a 24-hour clock:17/03/2026,09:05,1,234.5,74%, "3 minutes ago", and English collation. Until now it wrotede-DEwhatever the wording said (ADR-0024; ADR-0019 left this decision open on purpose). - German ships as a pair.
GERMAN_FORMATSstands besideGERMAN_WORDINGbehind@umriss-ui/core/wording/de, so a German application takes both halves of its language in one import:language={{ wording: GERMAN_WORDING, formats: GERMAN_FORMATS }}. An application that hands in the wording alone now gets German words over English notation - which is what the library shipped before, and why the decision was made. NumberInputreads the notation it writes. Its parser no longer assumes a German dot and comma; the separators are measured off the formats (separatorsOf), so a field under any notation - including one an application replaced - reads its own output back.parseNumberandfilterInputtake the separators as a last argument, defaulting to the default notation.- What did not move: the CSV export of
@umriss-ui/tablekeeps its decimal comma and its semicolon (a machine format for a spreadsheet, argued at its site), and the German test fixtures that measure a property of German stay German.
Added
separatorsOf(formats)andDEFAULT_SEPARATORS: the group and decimal separator a notation writes, measured rather than declared.- Three wording entries for
@umriss-ui/schedule's lane groups:scheduleFoldGroup,scheduleUnfoldGroupandscheduleLaneCount(count), in English and inGERMAN_WORDING. An application that supplies a completeWordingof its own gains three required entries — which the type says, at compile time, as it is meant to (ADR-0019).
Released together
@umriss-ui/core was already on latest; @umriss-ui/charts,
@umriss-ui/table and @umriss-ui/schedule join it with this delivery, and
the four are released together: the schedule stands on core and charts, and the
table's candidate peered on ^0.2.0 and would have refused this core.
The context menu (Sep. 2026)
From .scratch/schedule/spec.md, ticket 01, and
.scratch/schedule-refinement/spec.md, ticket 03.
Added
ContextMenu— the menu opened at a point instead of under a trigger, for a right-click on a surface that is not a button. Controlled (open,onOpenChange,positionin client coordinates,ariaLabel), built onPopover, withMenuItem,MenuSeparatorand the menu's keyboard. The focus returns to where it stood when the menu opened.- Wording:
scheduleLaneRefused,scheduleOverlap,scheduleViolatedDependency,scheduleGhostTimes,scheduleLeadIn,scheduleLeadOut,scheduleDependency,scheduleRoute,scheduleOverlapWithandscheduleViolatedBy, for@umriss-ui/schedule, in both shipped wordings. An application that hands in a wholeWordingobject of its own adds the nine.
0.2.0 – Styles that load themselves, and touch nothing else (Sep. 2026)
Delivery report for .scratch/styles-without-side-effects/spec.md (ADR-0021).
The second version on the registry. The middle digit rises because theme
leaves UmrissProvider and the base layer leaves the stylesheet - read
Changed before upgrading from 0.1.0.
Changed
- No stylesheet import any more.
dist/core.jsimports its own stylesheet;import { Button } from "@umriss-ui/core"is the whole setup.@umriss-ui/core/styles.cssstays exported and is optional. - No base layer. The rules on
html,body,*,:focus-visible,::selection,input/textareaand the page's scrollbars are gone. An application that relied on them for its own page - margin, font, background, box model, selection colour - sets them itself. Every component carries what it needs: its text context,box-sizingand squircle corners on its own elements and their::before/::after, a focus ring on every element it puts into the tab order (the calendar's days and month arrows, the range presets and the close buttons ofModalandToastincluded), caret, autofill look and scrollbars on its own fields and scroll containers. - Cascade layers. Every rule lies in
umriss.tokens,umriss.baseorumriss.components. CSS an application writes outside a layer wins over the library whatever its specificity and loading order, and a token is overridden with:root { --u-… }. - Light and dark are
color-scheme. Tokens with two values arelight-dark(<light>, <dark>).:root[data-theme="dark"]is gone andcolor-schemeis no longer set on:root: an application setscolor-scheme(most theme libraries already do), and one that sets nothing stays light. UmrissProviderhas notheme- the prop,UmrissConfig.themeand theThemetype are removed - and it writes nothing onto the document any more, including thedata-densityattribute no stylesheet read.densityworks as before, through context.- Browsers: Chrome 123, Firefox 120, Safari 17.5 or newer (
light-dark()).
0.1.0 – First publication (Sep. 2026)
The first version published to npm, under the tag latest. It is the state described by
every internal entry below, up to and including internal 0.10.0; nothing about
exports, props or behaviour changed for the publication itself.
- Installable from the registry:
pnpm add @umriss-ui/core. The German wording ships as@umriss-ui/core/wording/de(ADR-0019), the stylesheet as@umriss-ui/core/styles.css. - The demo is online at https://romanhaendler.github.io/umriss-ui/core/.
- The version number was reset from the internal
0.10.0to0.1.0— see The count started again at publication above.
internal 0.10.0 – The seam of a button group (Sep. 2026)
Appearance only; no export, prop or behaviour moves. The number stays where it is.
Fixed
- A
ButtonGroupdrew two lines at every seam instead of one. Every button draws its own edge, and it draws it on all four sides. Inside the group the two edges that face one another land exactly where the seam already is: a line over the full height, running hard into the rounded outline, beside the inset hairline that was meant to be the only line there. Three buttons read as three boxes shoved together rather than as one control. The edges that face into the group are now cut away; the edge above and below stays, because that is where the outline of the group comes from, and the first and the last button keep their outer side, rounded corner and all. - Groups of
primaryorghostbuttons are unchanged. The repair takes away the edge a button happens to have rather than drawing a new one, so a variant that carries no edge gains none here. Visible in the demo: the seam of the ghost group always looked the way the secondary group now does. - Keyboard focus keeps its whole ring. It stands four pixels out, and the cut would have sheared it off on exactly the sides that face into the group, so focus lifts the cut. Hover does not need to: its edge is one pixel and would stand where the seam already is.
internal 0.10.0 – The provider explains itself (Sep. 2026)
Documentation only; no export, prop or behaviour moves. The number stays where it is.
Fixed
<UmrissProvider wording={GERMAN_WORDING}>was never valid. The provider takeslanguage, and the wording sits inside it:language={{ wording: GERMAN_WORDING }}. The wrong form stood in the header ofsrc/lib/language/de.ts— the file a reader imports the German wording from — and in the entry below. The README always had it right. Both are corrected.
Added
- Four props that land in a generated table now carry their JSDoc:
UmrissProviderProps.portalTarget,.toastand.children, andLanguageOptions.formats. They appear in a props table for the first time, becauseUmrissProviderhas a demo page for the first time. - The demo has a page for the provider, with a worked example of taking the German wording whole and one of overriding entries and formats singly. Where the register was described before, it can now be read off two pictures.
internal 0.10.0 – One language, one scope (Sep. 2026)
Delivery report for .scratch/english-and-umriss-ui/spec.md. The number does not
move: nothing was ever published under it, and this is a rename rather than a
release.
Changed
The package is called
@umriss-ui/coreand its directory ispackages/core. The npm scope moved from@umriss/*to@umriss-ui/*, the org actually secured for this library; the sibling packages are@umriss-ui/tableand@umriss-ui/charts. Nothing was ever published under the old names — all three returned 404 from the registry — so there is no alias and no deprecation window. An import line changes from@umriss/uito@umriss-ui/core, and the stylesheet from@umriss/ui/styles.cssto@umriss-ui/core/styles.css. Nothing else about a call changes.coremeans the package you install first, not a layer everything sits on:@umriss-ui/chartsdepends on nothing and will keep depending on nothing (ADR-0016).Every identifier and every document is English (ADR-0018, which supersedes ADR-0015 and its carve-out that props are English while identifiers are German). For the twenty-one names that cross the package boundary this is a rename of the public surface, carried out in one commit: the wording is
Wordingand the formats areFormats,SpracheProviderisLanguageProviderwithuseWording,useFormatsandmergeLanguage,STANDARD_WORTLAUTandSTANDARD_FORMATEareDEFAULT_WORDINGandDEFAULT_FORMATS, the limit model isLimit,LimitSet,Assessment,Verdict,Severity,Side,assessandverdictWeight, freshness isFreshness,FreshnessAges,freshness,age,cadenceanduseFreshness, the window arithmetic isvisibleWindow,scrollForRow,useVirtual,VirtualRowsandVirtualOptions, and the density hook isuseDensityFor. The long prose headers that carry the design reasoning were translated, not shortened. Deprecated aliases were considered and rejected: they were right for the prop renames of ADR-0015, because there were callers; here there are none.English is the default wording, German ships as a subpath (ADR-0019).
DEFAULT_WORDINGis English, entry for entry, and every label the library draws without being told otherwise is now English. The German that used to be the default is unchanged — down to the thin spaces — and is exported as@umriss-ui/core/wording/de:import { GERMAN_WORDING } from "@umriss-ui/core/wording/de"; <UmrissProvider language={{ wording: GERMAN_WORDING }}>Both are typed
Wording, so an entry added to the interface and forgotten in the other language is a compile error rather than a missing label on a screen. Semantic drift between the two cannot be caught by anything and is accepted as the price of shipping two. A subpath and not a second entry in the main export: an application that never imports it never pays for it.The formats are still
de-DE.DEFAULT_FORMATSgroups and separates numbers, dates and percentages the German way, so the default currently renders English words around German digits — "43 of 1.204", a date as "17.03.2026". This was left alone on purpose and not for lack of noticing: a locale is not a language, changing it moves every number, date and percentage in the library, anden-GBanden-USdisagree about dates, so a library whose audience is unknown arguably should not pick either. The seam already works — theUmrissProvidertakesformats— and ADR-0019 records the state rather than hiding it.
Otherwise
The licence is MIT, and a LICENSE file ships in the package. No behaviour
changed anywhere in this delivery: where a rename revealed a defect it was
written down and fixed in a commit of its own, never folded into the rename.
Changed — the values, not only the names
The first pass renamed identifiers and left the values they hold standing. A value is read as often as the name that holds it — in the DOM, in a selector, in a screenshot's file name — so these moved too, each with every consumer:
Placeis"top" | "right" | "bottom" | "left". It was"oben" | "rechts" | "unten" | "links", and it stands in the DOM asdata-place. The wording registers translate it: the German one answers "oben" where the English one answers "top".- The limit model.
Verdictis"ok" | "unknown" | "warning" | "alarm",Severityis"warning" | "alarm",Sideis"upper" | "lower", and the fields arevalue,side,severity,limits,target,verdict,limit,excess,deviation. ADR-0006 holds this model twice, once in each package, so core,@umriss-ui/charts, the shared case table and the runtime conformance test moved in one commit.data-urteilisdata-verdict. Freshnessis"fresh" | "stale" | "lost"and its two ages arestaleandlost.data-aktualitaetisdata-freshness.useVirtualtakes{ rowHeight, overscan }and gives backfrom,to,fillerBefore,fillerAfter,count,showRow,onScroll.DateRangeis{ from, to }and aRangePresetcarries arange. A preset's key is now its wording key (today,last7Days).- The dock's eight tokens are
--u-dock-tool,-grip,-gap,-padding,-margin,-grip-travel,-refusal-surface,-refusal-duration. Three of them are read back at runtime, so whoever overrides them changes from how many tools on a side edge the dock refuses.
Fixed
- Virtualisation measured no row in
@umriss-ui/table.useVirtualre-measures the row height on a real row; it looked for[data-zeile], which only the tree writes — the table writesdata-row. The row height therefore stayed at the starting value the caller passes, and the filler rows were sized against it. Both attributes aredata-rownow.
internal 0.10.0 – What the table toolbar says (Sep. 2026)
Delivery report for .scratch/table-filters/spec.md: the entries of the wording
that @umriss-ui/table reads for its column filters. The components of this
package do not change.
Changed
resetAllsays "Reset" instead of "Reset everything". The button now stands in the table toolbar beside the ratio "43 of 1.204", where "everything" says nothing the place does not say already; in the empty body it does the same thing and is therefore called the same.removeConditionNamedno longer begins with "Filter": "Remove Line: Line 1". Beside it now stands a second button for the same condition, and both are built alike.
Added
editConditionNamed: the name of a condition that opens its filter — "Edit Line: Line 1, Line 2".moreValues: the rest of a long list within a condition — "+1".- The range filter of the table:
filterFromandfilterTolabel its two fields,rangeFromTo,rangeFromandrangeToname its condition — "100–500", "from 100", "to 500" — andrangeInvalidsays that "From" lies behind "To".
Removed
sucheBedingung, which had no English successor and is simply gone: the search no longer appears as a condition beside the field that already shows it.
internal 0.9.0 – The table moves out (Sep. 2026)
Delivery report for .scratch/umriss-table/issues/14-remove-table-from-ui.md.
The table and the alarm list now stand in @umriss-ui/table (ADR-0016,
ADR-0017). There, columns are elements typed against their rows, and the table
renders its rows itself — so a reordering in the model is on the screen as well.
The new package takes @umriss-ui/core as a peer and reads its provider, formats
and wording; one UmrissProvider configures both packages.
With this number appear the two sections below it as well, which had been unpublished until now.
Changed
- The table and the alarm list are no longer in this package, and three entries of the wording are struck. Whoever imports or overrides them no longer translates after the upgrade; what went where stands under "Removed". The section "Where the library contradicted itself" further down appears with the same number and has entries of its own under "Changed".
Removed
- The table:
Table,Th,Td,TableToolbar,TablePagination,TableEmpty,TableSkeletonRows,TableFilter,TableFilterList,TableFilterStrip,TableExpandButton,TableRowDetail,TableRowActions,TableVirtualBody,TableVirtualRowwith their props types andSortDirection,TableActiveFilter,TableFilterOption; the modeltableModel,column,sumwithColumn,SortLevel,TableInput,TableProjection; the companionuseCompanionwithCompanion,CompanionOptions,Sort;asCsv;alsSuchparameterandausSuchparameternwithTableViewandAusSuchparameternOptionen;useTableSelectionwithTableSelection. Instead@umriss-ui/table. Some names exist there again, in a new shape:useTable(rows, options)gives outTable,Columnand the remaining parts instead of taking a column field;columnbuilds a preset forColumn;alsSuchparameter,ausSuchparametern,useTableSelectionand their types are unchanged. The export ist.asCsv()and the partExport. AlarmListandalarmModelwithacknowledge,countAcknowledgeable,countInWindow,frequencyByType,detectFlood,nextLifecycleState,isActive,isAcknowledged,isDone,hasReturned,priorityRank,PRIORITIES,ALARM_COLUMNS,alarmColumns,DEFAULT_ORDERand their types. Instead the same names out of@umriss-ui/table; the props of the alarm list are calledview,selection,onAcknowledge,asOfandfreshnessthere.- Three entries of the wording that only the old table read:
zeileAufklappen,zeileZuklappen,filterEntfernen.@umriss-ui/tablenames the same buttons after the row (expandRowNamed,collapseRowNamed,removeConditionNamed). A provider that overrides the three no longer translates. All remaining entries of the table stay here (umriss-table 04).
Stays
- The window arithmetic (
visibleWindow,scrollForRow,useVirtual) anduseDensityFor— theTreeViewuses the one, every part with a density of its own the other, and@umriss-ui/tableboth.
internal 0.9.0 – What the table needs from outside (Sep. 2026)
Delivery report for .scratch/umriss-table/spec.md, ticket 04. The table moves
to @umriss-ui/table (ADR-0016); what it used to read from inside is now
reachable from outside. Ticket 04 removed nothing; the table itself went with
ticket 14 (the section above).
Added
- The window arithmetic as an export of its own:
visibleWindow,scrollForRow,useVirtualand their typesRowWindow,WindowInput,RowPlacement,VirtualRows,VirtualOptions. The arithmetic and the types previously came out only through the export of the table, the hook not at all; theTreeViewgoes on using them. useDensityFor– the density of a part: its own statement, otherwise the density expressly set on theUmrissProvider, otherwise its own default. UnlikeuseDensity()it tells "the provider says comfortable" apart from "nobody says anything".- Wording for
@umriss-ui/table, in a section of the directory of its own:columns,arrangeColumns,columnForward,columnBackward,exportLabel,exportFileName,tableSearchPlaceholder,tableSearchLabel,sucheBedingung,filterColumn,selectRow,selectAllRows,expandRowNamed,collapseRowNamed,rowActions,rowAction,rowActionsMenu,cellAbsentValue,booleanYes,booleanNo,footerSum,footerAverage,entries,selectedCount,noEntries,nothingMatchesFilters. They stand here and not in the new package, so that oneUmrissProviderswitches the wording of both packages with one statement. (sucheBedingungwent again with 0.10.0.)
internal 0.9.0 – Where the library contradicted itself (Sep. 2026)
Delivery report for .scratch/library-audit/spec.md.
Changed
Modal and CommandPalette report onClose exactly once. Until now every
gesture — cross, escape, background — arrived twice: once out of the gesture and
once more through the native close event that the exit triggered at the end.
Whoever counts on the closing, logs it or shows a message now sees one. A closing
the browser itself triggers still arrives.
Wording.zeitraumMitZeitPlatzhalter is gone. It never named a placeholder
but the panel of the DateTimeRangePicker. In its place come two entries of
their own: dateTimeRangePanel for the panel and dateTimeRangeClear for its
clear ×, which until now shared dateRangeClear with the DateRangePicker.
Whoever overrode the old entry gets a type error and rewrites it to
dateTimeRangePanel.
Wording has five new required fields, because five texts stood in the
components past the directory: filterReset and filterDone (the buttons in the
TableFilter), multiSelectSummary (the "3 / 12" in the panel of the
MultiSelect), columnAcknowledgement and columnAge (the two columns of the
alarm model that the list does not show). Whoever overrides partially notices
nothing; whoever builds a complete Wording object gets type errors.
A Toast with tone="danger" or "warning" announces itself as alert.
Until now the shared region carried role="status" for every tone, and an error
was more polite there than the same error in the Alert. The region now has two
live areas that stand before the first message arrives — status for the polite
tones, alert for warning and error — and every message lands in the one for its
tone, by the same table as for the Alert. On the screen the order stays the one
the messages came in.
FormField required sets aria-required. The comment had always promised
it; only the asterisk was set. Now Input, Select, Textarea, NumberInput,
Combobox, RadioGroup and Checkbox carry it — out of the same context they
take aria-describedby and aria-invalid from. The triggers of the pickers and
of the MultiSelect are buttons and do not carry it: there it would not be a
permitted attribute.
Ids that arise out of the caller's values have a different format.
Tab/TabPanel, the options of the RadioGroup, the boxes of the TreeView
and groups as well as rows of the CommandPalette no longer build their ids out
of the raw value: a space in it broke aria-labelledby, and two trees on one
page handed out the same id. Whoever read one of these ids from outside — in a
test, say — now reads a different one; it was never promised.
The danger Button writes dark on light in the dark theme. White measured
3.68:1 on the lighter danger surface of the dark theme; the type is now
--u-color-on-danger and turns the polarity like the accent. In the light theme
it stays white. The accessibility check no longer tolerates the pair.
The scrim behind the Modal is --u-color-scrim. It had a value of its own
and dimmed in the dark theme with the light one; in the light theme it is now as
light as behind the command palette.
UmrissProvider dichte takes effect. The setting was read by nothing. Now it
sets data-dichte at the root and is the default for the density of Table
and AlarmList ("comfortable" means "regular"). A provider without dichte
changes nothing — the alarm list then stays compact.
The Textarea shows its focus ring at :focus-visible, like every field
beside it; the hover edge of Textarea and RadioGroup is that of the remaining
fields.
A Tooltip inside a Modal is visible. It portalled to the body and thereby
lay behind the dialog — and with it every tooltip of a Dock inside one. It now
takes the same rule as the Popover: nearest <dialog>, then the portalTarget
of the UmrissProvider, then the body. With a portalTarget in the provider it
therefore lands there outside a dialog as well, instead of at the body.
Enter in the time field commits in the DateTimePicker too, as it already
did in the DateTimeRangePicker.
A row of the CommandPalette is chosen only with the primary button. A
right-click had already chosen.
wording.presets reaches the DateTimeRangePicker as well. It built its
quick select out of the German constant and passed over an override that the
DateRangePicker had long been taking.
New
Five tokens: --u-color-on-danger, --u-color-danger-active,
--u-color-edge-hover, --u-duration-exit and --u-duration-exit-collapse. The
two durations fall to zero under reduced motion; Modal and Toast read them at
runtime instead of carrying the figure a second time.
alarmColumns(wording) labels the columns of the alarm model out of a
wording, and AlarmInput.columns takes them. The labelling lands in the
projection's columns and from there in the column menu and the CSV header row.
ALARM_COLUMNS remains as the default labelling and is the same as
alarmColumns(DEFAULT_WORDING).
Fixed
"Now" in the DateTimePicker means now. In the second 02:30 of the clock
change it gave out the first, an hour too early.
An instant keeps its occurrence when reopened. DateTimePicker and
DateTimeRangePicker opened a value that was the later occurrence of a doubled
hour as the earlier one; the next "apply" then shifted it by an hour.
An onInput of one's own on the Textarea no longer takes anything away from
it. It was spread after the library's and replaced it; the counter and the
growing then stood still. Both now run one after the other, the caller's first.
The comments the props table shows say what the code does. NumberInput
clamps every reported value, not only on blur; the default page sizes of the
TablePagination are 10, 25 and 50.
internal 0.8.0 – A strip above the surface (Sep. 2026)
Delivery report for .scratch/floating-dock/spec.md.
New
<Dock> – the tools of a surface, above it instead of beside it. A strip of
characters that lies above one host surface and carries the actions for it. It
belongs to its host and never to the screen: two charts side by side have two
docks, and neither claims to act on the other. It is rendered into a positioned
container:
<div style={{ position: "relative" }}>
<MyChart />
<Dock
tools={[{ id: "raster", label: "Grid", icon: <GridGlyph /> }, …]}
defaultPlace="unten"
mode={mode}
onUse={(id) => …}
/>
</div>
Four resting places, and nothing in between. "oben" | "rechts" | "unten" | "links", controlled (place / onPlaceChange) or uncontrolled
(defaultPlace, default "unten"). The dock is moved by its grip: dragging
snaps into the edge whose zone the pointer reaches, and the four arrow keys on
the grip are those same four places — absolute and not relative. Corners are not
resting places: a corner names no orientation. Why the dock snaps while being
dragged instead of following stands in ADR-0013; why the change of orientation is
animated by hand, in ADR-0014.
A place without room refuses visibly. Nine tools lie, with the tokens
shipped, as 316 × 40 and stand as 40 × 316; on a wide, flat surface the first
fits and the second does not. Such places are not offered — the zone shows the
outline the dock would have there, instead of quietly doing nothing. If the host
becomes so small that the current place no longer fits, the dock goes to the
next one that does and reports that through onPlaceChange. No overflow menu, no
shrinking, no scrolling.
The one limit of that, spoken out: if the strip fits in neither orientation any more, the dock stays where it is and juts out beyond its host. There is then no place to give way to, and the alternatives — shrink, scroll, collapse — are one and all expressly rejected. Whoever puts a dock into a surface that can become smaller than the strip is long had better hide it.
The mode belongs to the caller. mode names at most one tool and without a
statement is none — a dock whose tools simply fire has no mode, and none is
preset to the first tool either. onUse(id) reports every taking;
onModeChange(id) reports the same event narrowed to "that would be a different
mode", and whether the taken id is a mode at all is for the application to
decide.
Three more characters in the set: GripGlyph, GridGlyph, MeasureGlyph —
drawn according to the specification in packages/core/docs/glyphs.md.
New tokens: --u-dock-tool, --u-dock-grip, --u-dock-gap,
--u-dock-padding, --u-dock-margin, --u-dock-grip-travel. They are not merely
style: the component reads them back at runtime, because the question "does this
place fit" is a calculation over exactly these values. Whoever changes them
thereby also changes from how many tools on a side edge refuses.
TableToolbar stays what it is. It is a strip in the flow above a table —
it takes room instead of covering, and it does not move. Nothing about it
changes, and it is not deprecated.
internal 0.7.0 – A window for searching (Sep. 2026)
Delivery report for .scratch/command-palette/spec.md.
New
<CommandPalette> – one list, one term, one window. Controlled like the
modal (open, onClose); the opening remains the application's decision. An
item is { id, label, group?, icon?, weight? }, and what is chosen is the id —
whether that leads to a jump or to something being executed is for the caller to
decide. Exactly that keeps places and commands under one term instead of
under two.
The resting state belongs to the caller. Without a statement the palette
shows nothing before the first character — a search field and not a menu, and
only so is the growth movement producible at all. Whoever wants something
standing there passes restingItems:
<CommandPalette items={ALL} restingItems={recentlyUsed} … />
A list and not a switch, because the interesting question is not "all or
none" but "which". Whoever really wants to show everything writes
restingItems={items}; whoever wants to show the five most recently used can do
that too — and a showAll could not. With long lists, "all" is anyway exactly
the state this component abolished.
The rows stand in your order and without markings: without a term there is no rank and nothing to mark. Everything else — arrow keys, enter, escape, the pointer rule — applies unchanged.
There is no placeholder prop. Every visible text — placeholder, window
name, empty line, key hints down to the legend "Esc" — comes out of the
Wording. A second way to set the same text is worse than one.
Functionally it stays small, on purpose: one list as a prop. No sources, no asynchrony, no preview, no "top hit", no memory. What is missing is reach — and none of it is visible.
The finder finds subsequences, not substrings. dtp finds
DateTimePicker. A hit at the start of a word beats one in the middle of a word,
a contiguous run beats a scattered one, the shorter name beats the longer at
equal quality; the group name is searched along and ranks below every find in a
name. The matched characters are drawn in the accent — a find that no substring
explains has to let itself be seen to be one. The formula itself is internal;
what is promised is the order.
weight is the hook for frequency or recency: it is added to the rank last. The
library expressly keeps no memory for it — remembering is the application's
business.
useCommandPaletteShortcut(onOpen). Binds ⌘K/Ctrl+K and /, including the
rule that every caller otherwise rediscovers through a bug report: / does
not open while the focus stands in an input, a textarea or a
contenteditable.
Three new tokens, public – so that overlays of one's own meet the palette
instead of guessing at it: --u-radius-xl (18px, one step above the card),
--u-color-material with --u-color-material-fallback and --u-material-blur
(the translucent surface with its opaque substitute colour), and
--u-color-scrim (the dimming behind an overlay). Both themes have values of
their own; the dark one is chosen by eye and did not come about by inverting.
The alpha of the material is a floor: the opaque colour beneath it carries
the legibility alone, the translucency supplies atmosphere and never contrast.
Whoever lowers the value gets a panel whose type is legible by luck. The reason
stands in docs/adr/0012-a-translucent-material-needs-a-floor.md. Where
backdrop-filter is missing or switched off, the pane falls back to the opaque
colour — quieter than intended, in every other respect right. The existing
overlays of the library do not move along; that is a decision of its own with
consequences of its own for the baselines.
Changed
Wording has ten new required fields for the palette: palettePlaceholder,
paletteField, palettePanel, paletteList, paletteNoFinds,
paletteFindCount, paletteHintMove, paletteHintChoose, paletteHintClose,
paletteKeyEsc. As before: whoever overrides partially notices nothing;
whoever has built a complete Wording object gets type errors.
They are deliberately entries of their own and not those of the combobox. Its
noMatches stays where it is: the palette finds finds, and the word "hit" is
taken in the glossary for the point detection of the charts. Two meanings under
one grep are exactly what the directory exists against.
Nothing changes in the behaviour of existing components. Modal and palette share
the entrance and exit choreography of the native <dialog> internally; the
times, the escape behaviour and the background rule of the modal are unchanged,
and the shared hook is not part of the public interface.
internal 0.6.0 – The value, the alarm and its age (Aug. 2026)
Delivery report for .scratch/judging-values/spec.md,
.scratch/shopfloor-instruments/spec.md and
.scratch/plant-at-a-glance/spec.md, this package's share.
New
The limit is a rule, not a colour. assess(), verdictWeight() and the
types Limit, LimitSet, Assessment, Verdict, Severity, Side. A limit
is a value, a side and a severity; a target is something else and is never
violated, only missed. The assessment has four outcomes — ok, unbekannt,
warnung, alarm — and the fourth is the reason for the module: an absent value
must not look like a good one. No null return value, because that invites a
?? "ok" at the call site. A violation is beyond a limit, not on it.
The same module also stands in @umriss-ui/charts — written twice, on purpose,
justified in ADR-0006 and held together by a conformance test. For callers that
means: a tile and the chart beneath it never contradict each other about the same
figure, and @umriss-ui/core brings no canvas library along for it.
<Stat> – the tile that reads its value. Value, unit, label, optionally a
target, limits, a history and an as-of time. It gets no tone: the rule is
pulled out of the call site, and a tone prop would push it back. It has no
trend arrow: a direction out of two points of a noisy signal is noise with an
arrowhead. It says its verdict as a word, not only as a colour. Without
limits it passes no verdict at all. There is deliberately no StatRow: a row of
tiles is a layout problem, and <Stat> aligns its inside itself.
<AlarmList> and alarmModel – the lifecycle of an alarm. The library takes
alarms in and generates none (ADR-0009). The lifecycle state is one field with
four values, not two booleans — otherwise every list loses the third: came,
went, and nobody saw it. On top of that: the default order priority →
acknowledgement → time, the frequency per alarm type, flood detection, a return
threshold (dead band) and bulk acknowledgement through the known selection
helper. A flood is marked, never suppressed. The model composes with
tableModel; all previous guarantees go on holding.
Freshness is a different axis from the verdict. freshness(), age(),
cadence() and the hook useFreshness(). Three states out of two thresholds:
frisch, alt, abgerissen. A stale value keeps its verdict (ADR-0010) —
it does not become "unknown", because when the connection drops exactly what the
human then needs would otherwise be lost. The cadence derives from the
thresholds; a tile that goes stale after five minutes does not tick sixty times a
minute.
Formats gains relative(ms) — "vor 3 Minuten", in the coarsest unit that still
works out. (The formats are de-DE; see the entry at the top.)
Changed
Wording has new required fields – some thirty, for assessment, freshness
and the alarm list. Whoever overrides the wording partially, as foreseen
(<LanguageProvider wording={{ … }}>), notices nothing of it: the statement was
and remains Partial. Whoever has built a complete Wording object themselves
gets type errors and adds the new entries — or writes
{ ...DEFAULT_WORDING, …own }, which is the better way anyway.
internal 0.5.0 – Configurable, very large, checked (Aug. 2026)
New
Formats and wording are overridable. LanguageProvider, useFormats(),
useWording(), mergeLanguage() as well as the defaults DEFAULT_FORMATS and
DEFAULT_WORDING. The wording is a directory of entries, named after what
they label — not a translation call. Whoever leaves an entry out gets the default
text, never a key name and never an empty string. At this point German was still
the only shipped language; since 0.10.0 English is the default and German ships
as @umriss-ui/core/wording/de (ADR-0019).
One place to configure. UmrissProvider holds exactly five things: theme
("light" | "dark" | "system", where "system" subscribes and takes a change at
runtime along), dichte, portalTarget, toast and language. With them
useUmriss(), useDensity(), usePortalTarget(), useToastConfig().
Virtualisation of very large tables. useTable(…, { virtual: { zeilenHoehe } }), <Table virtual={…}>, TableVirtualBody, TableVirtualRow and the pure
functions visibleWindow / scrollForRow. The sticky header and the sticky
first column stay; the keyboard reaches rows that were never rendered.
A character set. CrossGlyph, PlusGlyph, MinusGlyph – with a written-down
specification and a list of the characters that deviate from it (packages/core/docs/glyphs.md).
The set has grown since; what it holds today and what deviates stands in that
file, which is the actual deliverable.
Meter takes label.
Changed
The provider is voluntary. Without it every component behaves exactly as before. That is tested, not claimed: one component of every sort that reads configuration at all.
The row actions of the table are visible. They lay at opacity: 0.4 at rest
and thereby came to about 1.6:1 — by WCAG no longer readable text. Now 0.9.
That is a visible change: whoever uses the action column sees it more clearly
at rest than before.
Meter carries an accessible name. Without a label it says "Fill level".
Before, the role meter had no name at all.
A disabled Tag reports that in the accessibility tree (aria-disabled), no
longer only through a CSS class.
Virtualisation switches paging off. Whoever sets virtual gets no pages:
pageSize and setPage stay without effect, and the view carries neither page
nor pageSize. Both at once would be a control that contradicts itself.
Otherwise nothing changes about the contract. All formatters give out character for character the same as before — the checks for that were written before the move — and the glyph migration is pixel-identical.
internal 0.4.0 – Eight parts of the foundation (Aug. 2026)
The first version counted as it happened rather than reconstructed afterwards.
New
| Export | Purpose |
|---|---|
Textarea |
multi-line input; autoGrow with maxRows, showCount |
RadioGroup |
one out of few, with an explanatory line per option |
Alert |
a message that stays — five tones, optionally dismissible |
Tag, TagGroup |
a removable label; the group navigates with arrow keys |
Divider |
a separating line as a layout primitive, horizontal/vertical, with a label |
VisuallyHidden |
text only for the screen reader; focusable for skip links |
ButtonGroup, SplitButton |
buttons that belong together; a main action plus variants |
Text, Heading, Link |
typography on the token scale |
Two peculiarities that stand out at the call: Heading separates outline level
from size, so that nobody picks the wrong level in order to get the right size.
And the tone of the Alert determines its aria role, not the caller — the two
urgent tones interrupt the screen reader, the rest do not.
Changed
Nothing. No existing component was touched, no prop changed its meaning. This release is purely additive.
Otherwise
The package gets this changelog and a prepublishOnly step that runs the type
check and the build — a package that does not build can no longer be published by
accident. Nothing about the delivered content changes.
internal 0.3.0 – The table as a surface, part one (Aug. 2026)
New
| Export | What it is |
|---|---|
asCsv |
the filtered set as delimiter-separated text |
alsSuchparameter, ausSuchparametern |
the view as a link and back |
orderColumns |
column order, shared with the companion |
Column carries two new fields: label (column menu, CSV header row) and
hideable — false for columns that identify the row. TableInput takes
hidden and order; TableProjection gives the visible columns back in order,
and columnCount derives from that. useTable gains sortRankOf,
toggleColumn, columnChoices, setOrder, initialView and view.
asCsv returns text and triggers no download; file name and timestamp belong to
the application. Likewise the library does not write the address bar itself —
which is why the package hangs on no routing library.
Changed
- The sort is an ordered list of levels instead of a single one. The model still accepts a single level; existing calling code stays valid and yields the same order. What is new is the behaviour with a modifier key: without it, one actuation replaces the list and turns up/down as before; with it, it appends, turns, and at the end of the cycle takes itself out again.
Threports the modifier key toonSortas a second parameter and shows the rank from two levels on. Whoever callsonSortwith a one-parameter handler notices nothing of it.- Hiding does not reset the page. Unlike search, sort and page size it does not change the set of rows. Hiding is pure presentation: the pipeline goes on working with all columns, so that a sort by a hidden column is preserved.
Without a version of its own
Into the same period falls a rebuild of the two range pickers: the preset column, the month pair, the calendar configuration and the trigger now lie in shared, package-internal parts. For the caller nothing changes about it — no prop, no commit moment, no two-click rule, no class name — which is why it gets no number here.
internal 0.2.0 – Popover, and the logic behind the bodies (Aug. 2026)
New
Popover– the one dismissible, anchored surface: portal, position, outside click, escape with focus return, travelling along while scrolling, stacking order, entrance. Eight panel modules in the package have lain on it since; whoever builds anchored surfaces of their own can take the same primitive.DateTimePickerhassize– of the four pickers it was the only one missing it.Thgainscolumnand passes the identifier on toonSort.- The stacking order is a token scale (
--u-z-popover,--u-z-toast,--u-z-tooltip) and thereby shiftable from outside. A surface inside a<dialog>portals there and no longer lies behind it.
Changed
This release corrects behaviour that one may have arranged oneself around. The list in full:
NumberInputreports a clamped value on every route. Previously a key press gave out the unclamped value while blur and stepping gave the clamped one — one prop with two contracts that the caller could not tell apart. The text in the field still stays untouched while typing.DatePicker"today" gives out local midnight. The grid already did; the button supplied the time of day with it.DateTimePicker"now" goes through the time-zone resolution. It was the only way around it.- The choice between two identical wall-clock times (the doubled hour at the end of summer time) applies per day. Previously it was reset only on opening.
- Clearing while the panel is open closes it – in all four pickers. Previously a panel stayed standing that was filled out of a deleted value.
TablePaginationclamps internally. Without hits it said "page 1 of 1" while "next" was disabled.
Otherwise
The checkable logic of the components has since lain in modules of its own (German number notation, the month grid, the clock change, date and time formats, option lists, scale projection, position arithmetic, the table model). They are not part of the public interface; all that is visible of it is that these parts are now checked one by one.
internal 0.1.0 – Takeover state (Aug. 2026)
The state the package came into this repository with.
What is delivered is an ESM bundle, type declarations and a stylesheet under
@umriss-ui/core/styles.css; react and react-dom from version 18 on are peer
dependencies. Contained are Badge, Button, Card, Checkbox, Combobox, DataViz, the
four date and time pickers, EmptyState, FormField, Input, Layout, Menu, Modal,
MultiSelect, NumberInput, Select, Skeleton, Spinner, Table, Tabs, Toast, Tooltip
and useTableSelection.
The number was a placeholder figure, not a statement — it stood for "delivered once", not for a state one can update against. From 0.4.0 on, the rule above applies.