Skip to content
Twentyfirst UIPro

Changelog

What changed in each release of the Halo and Pro kits, the shared core, the CLI and the custom elements. All five packages are versioned together.

View as Markdown

0.6.0

Minor Changes

  • TextField: type="password" gets a show/hide button (reveal={false} turns it off), an interactive action slot that stays next to error marks, onKeyDown.
  • Checkbox: a 24px hit area around the same box (WCAG 2.2 target size).
  • SearchField: forwards id, name, defaultValue, onKeyDown, onFocus, onBlur, autoFocus and inputProps (combobox ARIA); bindShortcut focuses it with ⌘/Ctrl + the hint's key; the hint is hidden on touch screens.
  • FileDrop: disabled, name (the listed files take part in a form), onFiles can return false or the files to list.
  • OtpInput: empties its cells when error turns on (clearOnError), ref.current.reset().
  • New QrCode (encoded on the device, no service); TwoFactorSetup draws it from qrValue and offers trustDevice.
  • TilePicker layout="list".
  • CookieConsent: equalChoice, id, aria-describedby on its text, category texts may hold links.
  • Gallery: controlled index/onIndexChange, label, arrow keys on the stage and the thumbnails, focus moves into the lightbox and back.
  • BeforeAfter: the page scrolls vertically over it on touch screens, drags end on pointer cancel.
  • CountUp: renders its final value on the server and counts up on the client; animate={false}.
  • PlanCard: price takes any node, pricePrefix, priceNote, href/linkAs for the action, a "Current plan" badge.
  • FloatingLayout: controls and attribution in the free corner, startWidth/endWidth, --floating-safe-* variables with the space its slots take, the end slot as a bottom sheet below 720px (endSheet).
  • Tokens ramp-1…ramp-5 in both kits: a calm sequential ramp for maps and heatmaps.
  • Static WOFF fonts under fonts/og/ for share images (next/og) and PDF tools.
  • Every component now puts className and style on its root element (about 160 ignored one or both); a test renders every reference preview and checks it.
  • Nothing pushes past its container: segmented controls, tabs and toggle groups shrink with ellipsis labels (ToggleGroup block is new); MobileTabBar shares its width evenly; the TopBar nav switches to icons as soon as its labels no longer fit, the account button cuts name and address with an ellipsis (the full text in title) and the search field shrinks; CheckoutSteps stay on one row and show numbers only when narrow (compact forces it); an Alert's actions move under the text when narrow; chat source chips grow to two lines and take a detail tooltip; RangeSlider fields fit narrow columns.
  • TopBar and AppShell take a node as brand (a logo mark with the name).
  • SideRail: expanded/defaultExpanded/onExpandedChange show names next to the icons, toggle adds the button, every button has a real tooltip while collapsed.
  • Breadcrumb (and PageHeader crumbs): items with href (with linkAs) or onClick, onNavigate, label; plain labels are no longer fake # links; 24px targets.
  • Pagination: link mode (href per page, linkAs), label, previous/next disabled at the ends.
  • ListRow: href/linkAs, actions outside the clickable area (no nested buttons), a non-interactive row without onClick/href, titleLines/subtitleLines.
  • SelectMenu size="sm"; Table stack shows rows as labelled cards when the table is narrow.
  • Sidebar: variant (floating, attached, shell); links with href, linkAs and external; badge ("dot" or a word), disabled, item actions ("…" menu); collapsible groups with an action; tooltips and flyouts when collapsed; storageKey and shortcut (⌘B / Ctrl+B); an off-canvas drawer on phones (mobileOpen, onMobileOpenChange); header/footer as a function of { collapsed }.
  • Fixed: more room between a group heading and a selected first item; counts no longer jump to the corner in narrow shells.
  • Filters: as="aside" makes the sidebar a complementary region; items with checked are checkbox options (radios in a group with select: "single"), a group's content holds any control, collapsible turns the icons-only mode off.
  • New UserMenu; WorkspaceSwitcher takes compact.
  • AppShell navigation="sidebar" with sidebarGroups, workspaces, userMenu and sidebar.

0.5.0

Minor Changes

  • Everything product apps on the kit use, among them panelLeftClose, circleCheckBig, messageSquarePlus, replyAll, forward, lifeBuoy, gavel, octagonX, arrowDownUp and grid3x3.
  • New categories Games (swords, castle, pickaxe, dices, skull, chess pieces …) and Maps and 3D (draftingCompass, satellite, rotate3d, axis3d, lasso, spline …); more AI icons (brainCog, circuitBoard, scanText, textSearch, orbit, infinity …).
  • More brand logos: AI (brandOpenai, brandGooglegemini, brandMistralai, brandPerplexity, brandDeepseek, brandOllama, brandCursor, brandGithubcopilot, brandModelcontextprotocol …), business and marketing (brandLinkedin, brandMicrosoft, brandSlack, brandMeta, brandGoogleads, brandGoogleanalytics, brandTrustpilot), shops, infrastructure and automation (brandWoocommerce, brandShopify, brandWordpress, brandCloudflare, brandHetzner, brandSupabase, brandN8n, brandZapier …) and payments and crypto (brandRevolut, brandCoinbase, brandBinance, brandBitcoin, brandEthereum).
  • LinkedIn, Microsoft, Windows, Slack, Skype, Amazon and OpenAI come from Font Awesome Free (CC BY 4.0); the attribution is in ICONS-LICENSE.

0.4.0

Minor Changes

  • #37 c40834a Thanks @robintrepte! - An icon library: 567 more icons in the kit's style, from passkeys and security to social and brand logos, files, media, commerce, places, home and health.
  • @twentyfirstmedia/halo/icons (and /pro/icons, @twentyfirstmedia/ui-core/icons) exports each icon by name: fingerprint, scanFace, keyRound, shieldCheck, brandX, brandInstagram, brandGithub … Register what you use once with registerIcons({ fingerprint, brandGithub }) (or registerIcons(ICON_LIBRARY)), then use the names in Icon and every icon prop. Only registered icons are bundled.
  • Line icons are drawn on the kit's 24px grid with 2px round strokes (from Lucide, ISC); brand logos are filled (from Simple Icons, CC0). Licence notices ship in ICONS-LICENSE.
  • Your own icons: registerIcons({ acme: "M…" }), typed through the CustomIcons interface. The 93 built-in icons work as before and cannot be replaced.
  • An unknown icon name now logs a warning once instead of silently drawing a placeholder.
  • @twentyfirstmedia/elements registers the whole library, so <tf-button icon="fingerprint"> works in plain HTML; it also exports registerIcons.
  • New docs page "Icons": every icon with search, categories and copy on click. The MCP server's list_icons, search_docs and check_usage know the library.

0.3.1

Patch Changes

  • #35 3a25cd6 Thanks @robintrepte! - FloatingLayout on a phone keeps the height it is given: the stacked slot grid counts its padding into the inherited height (box-sizing: border-box). Before, the layout grew by twice the padding, and with a fixed height the bottom slot was cut off at the edge. Apps that worked around it with .tf-floating-ui { box-sizing: border-box; } can drop that rule.

0.3.0

Minor Changes

  • #30 bef588d Thanks @robintrepte! - Button and IconButton pass native attributes on to their <button> (or, with asChild, to the child): id, name, value, form, title, tabIndex, autoFocus, aria-label, aria-labelledby, aria-describedby, aria-expanded, aria-controls, aria-haspopup, aria-pressed, aria-current (documented as NativeButtonProps), plus any other <button> attribute, data-*, event handlers and, in React 19, ref. type also takes "reset", and IconButton takes type. On IconButton, aria-label or title override the ones label sets. With asChild, onClick and the other attributes now reach the child as well.

In @twentyfirstmedia/elements, <tf-button> and <tf-icon-button> take these as attributes; aria-* attributes move from the host to the inner button. The docs and the MCP server list them with both components.

  • New onFiles(files: File[]) with the File objects the person picked or dropped, after accept and multiple. Added list entries carry their file too, so onChange gets them as well.
  • Progress only when the app reports it: with onFiles, nothing is simulated; set progress on controlled files to show a bar (now a labelled progressbar). An uncontrolled list with onFiles shows each file with its size only.
  • accept is checked on drop as well; onReject receives what did not match. onRemove reports removed or cancelled files.
  • simulateProgress keeps the old demo animation; it stays the default without onFiles, so existing code behaves as before. Running simulations stop on unmount.
  • #31 ece54b1 Thanks @robintrepte! - Gallery and BeforeAfter load images lazily and take loading and referrerPolicy.
  • Gallery: the main image loads lazily by default (loading="eager" for a gallery at the top of the page), thumbnails always lazily, all images decode asynchronously; referrerPolicy applies to every image (new type ImageReferrerPolicy).
  • BeforeAfter: the slider has an aria-valuetext ("Before 40%, After 60%", with beforeLabel/afterLabel), overridable with valueText(percent); its accessible name is label. Page Up/Down, Home and End move the slider; arrow keys no longer scroll the page while it has focus. Both images take loading (default lazy) and referrerPolicy.
  • #29 4cf034a Thanks @robintrepte! - Pagination can be controlled: page with onChange(page) (1-based), or defaultPage with an optional onChange for an uncontrolled pager. page without onChange stays the start page, as before. Clicking the current page or past either end no longer reports a change.
  • UIProvider messages: replace any text a component shows on its own (accessible names such as "Close", "Send", "Previous image", "Select row", button texts, placeholders, status words, validation messages). Partial and nested; props such as title or label still win. English stays the default and no translations ship. useUIStrings(), DEFAULT_STRINGS, the UIStrings and UIMessages types, and one exported group per component file (MEDIA_STRINGS …).
  • UIProvider locale (BCP 47, default en-US): every number, amount, percentage and date in components, calendar month and weekday names. useFormat() returns the helpers bound to it; formatNumber, formatMoney, formatPercent and formatDate take an optional locale as their last argument.
  • CountUp and StatTile read string values in the provider locale ("13,05 €/m²" in de-DE counts to 13.05 instead of showing "1,305"). New helpers parseFormatted, formatLike, numberSeparators.
  • SearchField takes label (accessible name).
  • Fixed: VoiceMode with stateLabel showed "Listening…".
  • #32 3b0992d Thanks @robintrepte! - TextField passes native constraints to its input: min, max, step, minLength, maxLength, pattern, inputMode, readOnly and autoFocus (next to the existing name, required and id), all typed and documented. PlanCard takes disabled for its button.

0.2.1

Patch Changes

  • #26 863179b Thanks @robintrepte! - Published through npm Trusted Publishing: the release workflow no longer uses an npm token, and every package carries a provenance statement linking it to the GitHub Actions run that built it. No code changes.

0.2.0

Minor Changes

  • New Ambient: a calm, living background of soft colour fields from the kit (pastels in Halo, a faint aurora in Pro) that drift slowly, or a photo or video under a scrim, or your own layer. Still with motion off, paused off screen. New tokens --ambient-base, --ambient-1…4, --ambient-scrim.
  • In a glass region, AppShell turns into glass (top bar, rail, sidebar; the sheet clears), and StatTile and tinted cards lay their tint over the glass (--glass-tint).
  • On glass, the faintest text steps up one shade so secondary text stays readable.
  • AppShell applies className.
  • New pattern: a wellbeing dashboard in glass.
  • #22 beb3043 Thanks @robintrepte! - Glass surfaces for UI that floats over maps, 3D scenes, video or photos, in both kits: translucent, blurred and colour-lifted, soft and milky in Halo, dark with a hairline edge in Pro.
  • surface="glass" on Panel, Card, Dock and MapView (legend, controls, popup), and <UIProvider surface="glass"> for everything floating in a subtree: top-level surfaces, loose controls, menus, popovers, dialogs and drawers. What sits on glass (inputs, tab tracks, badges, highlights) turns translucent too; surface="solid" opts one out. Without React: class="tf-glass" or data-surface="glass".
  • New FloatingLayout: four slots (top, start, end, bottom) over a full-bleed background, glass by default; on phones the slots stack and the layout grows with them.
  • New glass tokens in both kits (--glass, --glass-strong, --glass-control, --glass-well, --glass-hover, --glass-border, --glass-highlight, --glass-sheen, --shadow-glass, --glass-filter), derived from the theme. Body text on glass keeps 4.5:1 over any backdrop; reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill.
  • MapView takes height="100%" (any CSS length).
  • Dock now applies className and style.
  • A UIProvider subtree with its own tokens keeps the outer theme, so derived colours follow the tokens even without a theme prop.
  • New pattern: a city map with glass controls.
  • <UIProvider motion="off" | "standard" | "expressive"> (or data-motion): expressive gives orchestrated page entrances (Halo: longer, more travel, a short focus-in blur; Pro: crisp and staggered), off stops everything, e.g. for a setting in your app. The reader's reduced-motion setting always turns motion off.
  • Choreography: charts, progress bars and CountUp wait for the card they sit in; a Stagger below the fold waits until it scrolls into view (reveal={false} plays at once).
  • Layers animate out as well as in: popovers, tooltips, hover cards, comboboxes, dialogs, drawers, the command palette and toasts. Modal, Drawer and CommandPalette take an optional open for that.
  • startViewTransition(update) for page transitions (the kits style ::view-transition), useMotion() for the level in effect, usePresence(open) and leaving() for your own layers.
  • New motion tokens: --dur-exit, --motion-time, --motion-travel, --motion-blur.
  • Fixed: with themes.css or scoped.css, keyframes defined in another file of the kit never ran (spinners, skeleton shimmer, pulses, pop-ins).

Patch Changes

  • #25 688f141 Thanks @robintrepte! - Glass app shell: it keeps the layout of the solid shell and turns its frame into glass, with the top bar and rail sitting on it as they sit on the solid frame (they no longer float as separate pieces). A ToggleGroup is only as wide as its items.

0.1.1

No changes in this release.

0.1.0

Minor Changes

  • #12 2cf6196 Thanks @robintrepte! - Consistency pass over every component in both kits and themes. The app shell responds to its own width (container queries); kit boxes no longer depend on the host's box-sizing; controls and code use the kit fonts; bold stays on the type scale; status shapes, list icons, stat values, pagination and invoice numbers line up; segmented controls and tabs no longer stretch or break mid-word. Gauge, Funnel and ProgressRing now fill when reduced motion is on. Pro follows its reference more closely: decorative tints and chart series are monochrome, bars are flat, in-flow cards use hairlines, badges sit on the corner. New: formatPercent.
  • #8 2db7b83 Thanks @robintrepte! - First release: every component of the Halo reference in ui-core, the Halo and Pro kits with the same components, props and markup (switchable at runtime via data-kit), tokens and a Tailwind v4 preset per kit, self-hosted fonts and the CLI (init, add, list, diff).
  • #10 532f835 Thanks @robintrepte! - Mobile first: every component works from 320px up in both kits (desktop from 1181px unchanged). The shell, rail and page nav reflow on phones, tables scroll in their own box, dialogs open as bottom sheets, popovers and tooltips stay on screen, text fields use 16px on touch screens and Pro switches to comfortable hit targets on touch unless data-density is set. New: tf-grid columns via --cols / --cols-md / --cols-sm, tf-sheet-nav / tf-sheet-aside, tf-table-scroll, useKeepInView, useScrollFocus, Tooltip defaultOpen.
  • #16 350ca53 Thanks @robintrepte! - Open source under the MIT license and published on npmjs.com: install with npm i @twentyfirstmedia/halo (or pro), no registry setup or token. twentyfirst-ui init no longer writes an .npmrc.
  • #13 559cb2d Thanks @robintrepte! - English only: built-in text is plain English and overridable through props; numbers, money, percentages and dates format with Intl in en-US (formatNumber, formatMoney, formatPercent, formatDate).

New components in both kits: Accordion, Separator, AspectRatio, Label, Toggle, ToggleGroup, ButtonGroup, ScrollArea, HoverCard, Carousel, Menubar, NavigationMenu, Sidebar, SelectMenu, Form and ResizablePanels. Menu gains keyboard navigation, typeahead, submenus, checkbox/radio and disabled items; Tabs render panels; Drawer takes side; Popover flips when there is no room; CommandPalette takes loading, empty and onQueryChange; Button and IconButton take asChild; inputs take id, name and required.

  • #18 0f19447 Thanks @robintrepte! - shadcn-style theming in both kits. The kits use shadcn/ui's theme variables (background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring, radius, chart-1 … chart-5, sidebar-*) and every component reads them, so a pasted shadcn theme restyles everything, light and dark. The old --accent/--on-accent/--accent-hover are now --primary/--primary-foreground/--primary-hover; --accent is the highlight under the pointer, as in shadcn. Token blocks have zero specificity and dark mode also follows the .dark class. New: themeCss, parseThemeCss, brandTheme, THEME_PRESETS and preset CSS files (themes/blue.css …), UIProvider tokens, twentyfirst-ui theme, Tailwind utilities with shadcn's names.

Patch Changes

  • #15 2c5ddb6 Thanks @robintrepte! - The CLI ships an MCP server for coding agents: twentyfirst-ui mcp (stdio, offline, the docs are bundled) with the tools search_docs, list_components, get_component, get_pattern, get_guide, get_tokens, list_icons and check_usage, every page as a resource, and the prompts build_screen and review_ui. twentyfirst-ui mcp init registers it for Claude Code, Cursor and VS Code and adds the kit rules to AGENTS.md.
  • #11 acec782 Thanks @robintrepte! - Icons are centred: 36 icon drawings were off the middle of their 24px grid (up to 1.75 units), visible in round icon buttons and tinted icons. They are now centred on their bounding box; shapes are unchanged, and play stays optically centred.