umriss-ui

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

0.39.0 – Toasts that stay, and one provider (Oct. 2026)

Changed

Added

0.38.0 – Holding the toasts back, and a deck that glides (Oct. 2026)

Added

Changed

0.37.0 – The toasts' distance from the window's sides (Oct. 2026)

Added

0.36.0 – Toasts stand above a modal dialog (Oct. 2026)

Changed

Fixed

0.35.0 – React 19 is the floor, and an import pays for what it uses (Oct. 2026)

Changed

Fixed

Otherwise


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

Fixed


0.33.0 – The library's one voice, and React 18 proved (Oct. 2026)

Added

Changed

Fixed


0.32.0 – Words for a snooze's reason and a plan's milestones (Oct. 2026)

Added


0.31.0 – One natural width of twenty characters (Oct. 2026)

Added

Changed

Fixed

0.30.0 – A card hands its height to its body (Oct. 2026)

Changed

0.29.0 – A calculation's empty sum (Oct. 2026)

Added

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

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

Added

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

Changed

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

Changed

0.24.0 – The select's own list (Oct. 2026)

Changed

Fixed

0.23.0 – A multiselect that clears (Oct. 2026)

Added

Fixed

0.22.0 – A tag keeps its descenders (Oct. 2026)

Added

Fixed

0.21.2 – Text that reads in a tooltip (Oct. 2026)

Fixed

0.21.1 – A long list that stays (Oct. 2026)

Fixed


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

Changed

0.20.0 – A small multiselect and combobox (Oct. 2026)

Added

0.19.0 – A tree model that takes a match (Oct. 2026)

Added

0.18.0 – Buttons a step tighter (Sep. 2026)

Added

Changed


0.17.0 – Tighter buttons, and a button for an icon (Sep. 2026)

Added

Changed


0.16.0 – A toast of its own, and room on a phone (Sep. 2026)

Added

Changed

Fixed


0.15.0 – A step for dense surfaces (Sep. 2026)

Added

Fixed


0.14.1 – Found by search (Sep. 2026)

Fixed


0.14.0 – Words for a table's rows (Sep. 2026)

Added


0.13.1 – A round end point in Safari (Sep. 2026)

Fixed


0.13.0 – A tile's history across the tile (Sep. 2026)

Added

Changed


0.12.1 – A popover's own surface (Sep. 2026)

Changed


0.12.0 – Popovers that stay by their trigger (Sep. 2026)

Added

Changed

Fixed


0.11.0 – Data-dense applications (Sep. 2026)

Added

Changed


Fixed

0.10.0 – Everything the comparison asked for (Sep. 2026)

Added

Changed

Fixed


0.9.2 – Today's dot, one chevron (Sep. 2026)

Changed


0.9.1 – Depth work on the pickers and the select family (Sep. 2026)

Fixed


0.9.0 – A token for figures (Sep. 2026)

Added


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

Changed

Fixed


0.7.0 – A text token for danger (Sep. 2026)

Added

Changed


0.6.0 – The wording of the grouping (Sep. 2026)

Added


0.5.0 – The wording of the calculation (Sep. 2026)

Added


0.4.0 – One focus, and fields on the iPhone (Sep. 2026)

Changed

Fixed

0.3.2 – Phones, touch and a review (Sep. 2026)

Changed

Fixed


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

Added

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


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


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.


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

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

Added

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

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:

Fixed

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

Added

Removed

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

Removed

Stays

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

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

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

Changed

This release corrects behaviour that one may have arranged oneself around. The list in full:

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.

Suggest an edit on GitHub