@remix-run/ui
0.13.0 · 23 Oct 2026MIT19.4k / weekRepositoryHomepagenpmAtom feed
17 stable releases, newest first, by release line. Notes are quoted as the maintainers published them.
- 0.13.x · latest line
0.13.0
23 Oct 2026- breaking
Prop types for the previously removed styled
Accordion,Select, andComboboxcomponents have also been removed:AccordionProps,AccordionItemProps,AccordionTriggerProps,AccordionContentProps,SelectProps,SelectOptionProps,ComboboxProps, andComboboxOptionProps. Accordion's unusedheadingLevelprop and corresponding context fields, along with theAccordionHeadingLeveltype, have also been removed. - breaking
The toggle mixin is now exported as
toggleinstead ofcontrol. Sincetoggleconsists of a single mixin, it can now be called directly astoggle()rather than grouped undertoggle.control().-import * as toggle from '@remix-run/ui/toggle' +import { toggle } from '@remix-run/ui/toggle' -<input mix={toggle.control({ defaultChecked: true })} /> +<input mix={toggle({ defaultChecked: true })} />The options type has also been renamed from
ToggleControlOptionstoToggleOptions:-import type { ToggleControlOptions } from '@remix-run/ui/toggle' +import type { ToggleOptions } from '@remix-run/ui/toggle' Check the user's current motion preference with
prefersReducedMotion()from@remix-run/ui/animation. It returnsfalseduring server rendering or whenmatchMediais unavailable, so it can be called safely from component render functions as well as browser event handlers.import { animateLayout, prefersReducedMotion, spring } from '@remix-run/ui/animation' function Card() { return () => ( <div mix={animateLayout(prefersReducedMotion() ? false : { ...spring('snappy') })}>Details</div> ) }
Minor Changes
- breaking
Prop types for the previously removed styled
Accordion,Select, andComboboxcomponents have also been removed:AccordionProps,AccordionItemProps,AccordionTriggerProps,AccordionContentProps,SelectProps,SelectOptionProps,ComboboxProps, andComboboxOptionProps. Accordion's unusedheadingLevelprop and corresponding context fields, along with theAccordionHeadingLeveltype, have also been removed. - breaking
The toggle mixin is now exported as
toggleinstead ofcontrol. Sincetoggleconsists of a single mixin, it can now be called directly astoggle()rather than grouped undertoggle.control().-import * as toggle from '@remix-run/ui/toggle' +import { toggle } from '@remix-run/ui/toggle' -<input mix={toggle.control({ defaultChecked: true })} /> +<input mix={toggle({ defaultChecked: true })} />The options type has also been renamed from
ToggleControlOptionstoToggleOptions:-import type { ToggleControlOptions } from '@remix-run/ui/toggle' +import type { ToggleOptions } from '@remix-run/ui/toggle' Check the user's current motion preference with
prefersReducedMotion()from@remix-run/ui/animation. It returnsfalseduring server rendering or whenmatchMediais unavailable, so it can be called safely from component render functions as well as browser event handlers.import { animateLayout, prefersReducedMotion, spring } from '@remix-run/ui/animation' function Card() { return () => ( <div mix={animateLayout(prefersReducedMotion() ? false : { ...spring('snappy') })}>Details</div> ) }
Patch Changes
Server rendering skips setup and render for animation mixins, keyboard and outside-click helpers, typeahead behavior, and popover anchor and focus helpers. Mixins that provide initial attributes, styles, or ARIA props still run on the server.
Bumped
@remix-run/*dependencies:
- 0.12.x
0.12.1
1 Oct 2026Bumped
@remix-run/*dependencies:
0.12.0
1 Oct 2026- breaking
The core component APIs provided by
@remix-run/uihave now moved to@remix-run/component.@remix-run/uinow provides headless, accessible UI primitives and animation utilities for Remix components.The package root and its JSX runtime, server-rendering, test, development-refresh, and styled-component exports are no longer available. Move runtime imports to
@remix-run/componentorremix/component:-import { createRoot, css, on } from '@remix-run/ui' +import { createRoot, css, on } from '@remix-run/component'If
jsxImportSourcecurrently points to@remix-run/uiorremix/ui, move it to the corresponding component entrypoint:{ "compilerOptions": { "jsx": "react-jsx", - "jsxImportSource": "@remix-run/ui" + "jsxImportSource": "@remix-run/component" } }Primitive APIs retain their existing contracts but now use flat package subpaths:
-import * as accordion from '@remix-run/ui/accordion/primitives' +import * as accordion from '@remix-run/ui/accordion'
The package exports the
accordion,anchor,animation,combobox,listbox,menu,popover,select,tabs, andtogglesubpaths. The previous styledbreadcrumbs,button,checkbox,input, andradiomodules have been removed.@remix-run/uiis currently unstable and versioned independently. It is not available through theremixpackage and must be installed and imported directly. For an application that otherwise uses theremixpackage:npm i remix @remix-run/ui
If the asset server restricts imports with
allowPackages, add@remix-run/uinow that these APIs are imported from the standalone package:{ "assets": { - "allowPackages": ["remix"] + "allowPackages": ["@remix-run/ui", "remix"] } }Direct-package consumers should install both
@remix-run/componentand@remix-run/uiwhen they use the component runtime with the primitives or animation utilities. Bumped
@remix-run/*dependencies:
- 0.11.x
0.11.0
28 Sep 2026Preserve client-owned attributes during frame reloads with
data-rmx-preserve-attrs="class data-theme"on any matched element, including<html>and<body>(see #11809). The named attributes keep their live values or absence while other attributes and children reconcile normally. The incoming HTML controls the list; clearing it or removing a name returns those attributes to normal reconciliation.Send
X-Remix-Frame: truefrom the default browser resolver for all frame loads, including top-frame navigation and reloads. Named frames also sendX-Remix-Target, allowing handlers to identify and target frame requests consistently across browser and server rendering (see #11941).Treat marker-less HTML that starts with a doctype or
<html>as a document reload, so pages rendered withrenderToStringcan be navigated by the client runtime (see #11808).
Minor Changes
Preserve client-owned attributes during frame reloads with
data-rmx-preserve-attrs="class data-theme"on any matched element, including<html>and<body>(see #11809). The named attributes keep their live values or absence while other attributes and children reconcile normally. The incoming HTML controls the list; clearing it or removing a name returns those attributes to normal reconciliation.
Patch Changes
Send
X-Remix-Frame: truefrom the default browser resolver for all frame loads, including top-frame navigation and reloads. Named frames also sendX-Remix-Target, allowing handlers to identify and target frame requests consistently across browser and server rendering (see #11941).Treat marker-less HTML that starts with a doctype or
<html>as a document reload, so pages rendered withrenderToStringcan be navigated by the client runtime (see #11808).Keep hash-only navigations within a fully loaded page and download requests under browser control without reloading a frame. If a hash navigation interrupts a pending frame load, reload the destination so the previous page is not left visible. Preserve downloads even when another navigation listener changes the source link's
downloadattribute.Preserve parent component context for client entries rendered inside a
<Frame>, including setup-time context reads when the provider module loads later and after frame reloads. Keep nested frame content interactive when its owning client entry module is already cached (see #11894).Preserve server-rendered textarea values when a hydrated client entry first updates, without overwriting uncontrolled user edits.
Allow client entry props to use ordinary interfaces, including nested objects whose properties are all optional (see #11918).
Document custom mixin setup, lifecycle events, and deferred host removal with
event.persistNode(), including cancellation and keyed-node reclamation. Expand public API documentation for root, frame, context, anchoring, CSS mixin, and server-rendering helpers, and correct the popover, context, and anchoring examples.
- 0.10.x
0.10.0
18 Sep 2026- breaking
Navigations that specify a named frame that is not currently mounted now perform a document navigation instead of reloading the top frame. Fresh links, forms, and
navigate()calls are left to the browser, preserving native form methods and bodies, while back and forward traversal reloads the destination document. Omit the target when the navigation should always reload the top frame:-<a href="/account" data-rmx-target="optional-account"> +<a href="/account">
- breaking
The default
resolveFramenow only fetches same-origin sources and follows same-origin redirects. Apps that load cross-origin frame content must provide a customresolveFrametorun().Validate navigation source overrides regardless of the target, falling back to document navigation for invalid or cross-origin overrides.
- breaking
Raw HTML props now require an opaque value created by
unsafeHTML(). This applies toinnerHTMLand both iframesrcDocspellings (srcDocandsrcdoc). It prevents attacker-controlled prop spreads from activating HTML parsing with plain strings or JSON-shaped objects.outerHTMLis not supported because it would replace a reconciler-owned element.unsafeHTML()is an explicit authorization boundary; it does not sanitize or otherwise modify its input.-import type { Handle } from 'remix/ui' +import { unsafeHTML } from 'remix/ui' +import type { Handle } from 'remix/ui' function Content(handle: Handle<{ html: string }>) { - return () => <div innerHTML={handle.props.html} /> + return () => <div innerHTML={unsafeHTML(handle.props.html)} /> }
Minor Changes
- breaking
Navigations that specify a named frame that is not currently mounted now perform a document navigation instead of reloading the top frame. Fresh links, forms, and
navigate()calls are left to the browser, preserving native form methods and bodies, while back and forward traversal reloads the destination document. Omit the target when the navigation should always reload the top frame:-<a href="/account" data-rmx-target="optional-account"> +<a href="/account">
- breaking
The default
resolveFramenow only fetches same-origin sources and follows same-origin redirects. Apps that load cross-origin frame content must provide a customresolveFrametorun().Validate navigation source overrides regardless of the target, falling back to document navigation for invalid or cross-origin overrides.
- breaking
Raw HTML props now require an opaque value created by
unsafeHTML(). This applies toinnerHTMLand both iframesrcDocspellings (srcDocandsrcdoc). It prevents attacker-controlled prop spreads from activating HTML parsing with plain strings or JSON-shaped objects.outerHTMLis not supported because it would replace a reconciler-owned element.unsafeHTML()is an explicit authorization boundary; it does not sanitize or otherwise modify its input.-import type { Handle } from 'remix/ui' +import { unsafeHTML } from 'remix/ui' +import type { Handle } from 'remix/ui' function Content(handle: Handle<{ html: string }>) { - return () => <div innerHTML={handle.props.html} /> + return () => <div innerHTML={unsafeHTML(handle.props.html)} /> }
Patch Changes
Ignore invalid host prop names and reserved DOM mutation properties during server rendering and client reconciliation. Block
javascript:URLs in executable URL attributes while preserving other URL schemes and non-executable attributes. Use theon()mixin for events; the explicitinnerHTMLAPI and standard DOM,data-*,aria-*, SVG, and custom-element properties continue to work as before.Fix duplicated text during hydration when a browser splits long server-rendered text into multiple DOM nodes, including chunks that span adjacent text children (see #11591).
Avoid attaching duplicate event handlers when a client entry imports and renders another client entry, including through fragments and wrapper components. Preserve deferred removal and exit animations when removing nested client entries (see #11844).
Restore the previous named frame from
handle.frames.get(name)when a more recently mounted frame with the same name unmounts.
- 0.9.x
0.9.0
8 Sep 2026Client entries can now provide import maps through
resolveClientEntry(). The<ImportMap>component fromremix/ui/servercombines your mappings with those from blocking client entries so the initial document contains one complete import map. New mappings from later frame responses are installed before their client entries load. Plain<script type="importmap">elements remain supported when you do not need to combine mappings (see #11706).When a frame response changes an installed import mapping or integrity value, Remix loads a fresh document so navigation after a deployment cannot mix old and new modules.
run()now accepts aprocessClientEntryPreloadscallback to handle preloads for client entries discovered after the initial page load. Use it withremix/multiple-import-maps-polyfillto preload modules in browsers that need the polyfill, returning an empty array to skip native modulepreload links. See the client entry setup example (see #11706).handle.update()now warns and skips the extra render when called during component setup. Calls during rendering, or before the initial commit from outside setup, report a clear component error. Move these updates into an event handler or ahandle.queueTask()callback (see #11795).
Minor Changes
Client entries can now provide import maps through
resolveClientEntry(). The<ImportMap>component fromremix/ui/servercombines your mappings with those from blocking client entries so the initial document contains one complete import map. New mappings from later frame responses are installed before their client entries load. Plain<script type="importmap">elements remain supported when you do not need to combine mappings (see #11706).When a frame response changes an installed import mapping or integrity value, Remix loads a fresh document so navigation after a deployment cannot mix old and new modules.
run()now accepts aprocessClientEntryPreloadscallback to handle preloads for client entries discovered after the initial page load. Use it withremix/multiple-import-maps-polyfillto preload modules in browsers that need the polyfill, returning an empty array to skip native modulepreload links. See the client entry setup example (see #11706).
Patch Changes
handle.update()now warns and skips the extra render when called during component setup. Calls during rendering, or before the initial commit from outside setup, report a clear component error. Move these updates into an event handler or ahandle.queueTask()callback (see #11795).Fixed delayed scroll resets and history scroll restoration during frame navigation. Scroll now updates once the destination and its blocking frames first render, without waiting for the rest of the streamed content or client entry hydration. Also fixed scroll jumps in Chromium and stale or repeated scroll changes during redirects and overlapping reloads (see #11755).
Failed
frame.reload()calls no longer cause a second unhandled promise rejection when the caller already handles the error.Browsers without
NavigateEvent.sourceElementsupport now use full document navigation, so links and forms keep working when frame navigation is unavailable (see #11820).Frames now render HTML responses with
3xxand4xxstatus codes, so form validation messages and error pages appear in the frame when using the default resolver. HTML content types are now recognized regardless of case (see #11823).
- 0.8.x
0.8.0
31 Aug 2026- breaking
Remix UI framework-owned DOM attributes now consistently use the
data-rmx-*namespace. Renamermx-document,rmx-target,rmx-src,rmx-history,rmx-reset-scroll,rmx-preserve-dom, anddata-keytodata-rmx-document,data-rmx-target,data-rmx-src,data-rmx-history,data-rmx-reset-scroll,data-rmx-preserve-dom, anddata-rmx-key. Generated style and module preload markers now usedata-rmx-styleanddata-rmx-module-preloadinstead ofdata-rmx. - breaking
During server rendering, script elements with non-string children previously serialized those children as escaped HTML text. They now render empty and report an error. Pass a single string child, such as
JSON.stringify(value), to embed script content without HTML entity escaping. Script-tag sequences that could terminate the element remain escaped. - breaking
Remove
addEventListeners(). Use nativetarget.addEventListener(type, listener, { signal })instead. If a listener used the helper's second callback argument, create anAbortControllerand abort it when the listener runs again or its lifetime signal aborts.
Minor Changes
- breaking
Remix UI framework-owned DOM attributes now consistently use the
data-rmx-*namespace. Renamermx-document,rmx-target,rmx-src,rmx-history,rmx-reset-scroll,rmx-preserve-dom, anddata-keytodata-rmx-document,data-rmx-target,data-rmx-src,data-rmx-history,data-rmx-reset-scroll,data-rmx-preserve-dom, anddata-rmx-key. Generated style and module preload markers now usedata-rmx-styleanddata-rmx-module-preloadinstead ofdata-rmx. - breaking
During server rendering, script elements with non-string children previously serialized those children as escaped HTML text. They now render empty and report an error. Pass a single string child, such as
JSON.stringify(value), to embed script content without HTML entity escaping. Script-tag sequences that could terminate the element remain escaped. - breaking
Remove
addEventListeners(). Use nativetarget.addEventListener(type, listener, { signal })instead. If a listener used the helper's second callback argument, create anAbortControllerand abort it when the listener runs again or its lifetime signal aborts. Added a browser-only SPA response protocol to
remix/uifor associating bodyless route responses with Remix UI nodes. Application code uses the higher-levelrender()andrun()APIs fromremix/spa.
Patch Changes
Prevent aborted
renderToStreamrequests with multiple blockingFrames from producing unhandled promise rejections that can crash Node servers.Changed the scheduler's cascading update guard to warn when many component updates happen in one event-loop turn and only throw the infinite-loop error when a single component instance repeatedly updates itself. This keeps large
clientEntryhydration bursts interactive while still surfacing component names and counts for diagnosis.Fixed
mixprop types to accept argument-bearing mixins authored for a base element type on compatible subtype elements.Gracefully degrade to document navigations for browsers that do not support the Navigation API (see #11665).
Fix
data-rmx-reset-scroll="false"andnavigate(..., { resetScroll: false })to preserve the current scroll position. Default navigations now leave scroll resets and history restoration to the browser.Adjust CSS escaping to preserve CSS range media queries such as
@media (width < 900px)in server-renderedcss()output while continuing to neutralize literal closing</style>tags.Restore saved scroll positions for intercepted back and forward navigations when client entry reconciliation temporarily shrinks the document or triggers scroll anchoring, while continuing to wait for nested blocking frames before the Navigation API restores scrolling.
Prevent Safari Navigation API scroll resets from desynchronizing page hit testing after intercepted push and replace navigations (see WebKit bug 309542).
- 0.7.x
0.7.0
17 Aug 2026run()now uses a default browser frame resolver whenresolveFrameis omitted. Apps that supplied a resolver only to fetch frame HTML can remove it:let app = run({ loadModule, - resolveFrame(src, options) { - return fetch(src, { - headers: { Accept: 'text/html' }, - signal: options?.signal, - }) - }, })All
run()calls now enable frame reloads and same-origin link and form navigation through the Navigation API. The default resolver submits the requested method, encoding, and form data, and rejects non-OK responses. Keep a customresolveFramewhen the app needs custom request headers, body encoding, response handling, or error UI. Addrmx-documentto a link or form to leave that navigation to the browser (see #11693).
- 0.6.x
0.6.0
17 Aug 2026Allow
resolveClientEntry()to return module preload hrefs so client entries can preload their browser module graphs
- 0.5.x
0.5.0
14 Aug 2026- breaking
Browser
resolveFramecallbacks now receive a single options object instead of positional signal and target arguments. UpdateresolveFrame(src, signal, target)implementations to useresolveFrame(src, options)and readoptions?.signalandoptions?.target. Added
framesto theappobject returned fromrun(), mirroring the existinghandle.framesAPIAdded
@remix-run/ui/dev/refreshfor development tooling that needs to reconcile mounted roots after component modules update.
Minor Changes
- breaking
Browser
resolveFramecallbacks now receive a single options object instead of positional signal and target arguments. UpdateresolveFrame(src, signal, target)implementations to useresolveFrame(src, options)and readoptions?.signalandoptions?.target. Added
framesto theappobject returned fromrun(), mirroring the existinghandle.framesAPIAdded
@remix-run/ui/dev/refreshfor development tooling that needs to reconcile mounted roots after component modules update.Same-origin forms now progressively enhance into frame navigations when
run({ resolveFrame })is configured. Native constraint validation runs before interception, submissions target the top frame by default,rmx-targetselects a named frame, andrmx-documentopts back into document navigation. For non-GET submissions, resolvers receive the browser's nativeFormDataplus the selected method and encoding, and remain responsible for request encoding and_methodconventions. Non-GET submissions to the current URL replace its history entry without retaining theirFormData; submissions to a different URL and GET submissions push a new entry.Browser frame resolvers may now return a
Response. Its body is streamed into the frame, and when a top-frame navigation follows a redirect, the final response URL replaces the current navigation entry and becomes the frame's canonicalsrcwithout loading the frame a second time. Direct reloads and named-frame navigations render the redirected response without replacing their canonicalsrcwith the final response URL.Add an
rmx-history="push|replace"attribute for anchors and forms that overrides the history behavior of enhanced frame navigations. Native anchors usinglink(href, { history })emit the corresponding attribute value automatically.Add an
rmx-preserve-domattribute that tells the DOM reconciler to preserve a matching element's current attributes and children during reloads, allowing client-owned subtrees such as custom elements to manage their own DOM.
Patch Changes
Allow element-wide mixins such as
css()to be used on subtype hosts like<select>without TypeScript assignability errors.Built-in styled components now use adaptive
light-dark(...)colors for their internal surfaces, text, borders, focus rings, and control states so they render correctly in dark color schemes.Escape less-than characters in server-rendered
css()output so style values cannot terminate the generated<style>element.Fix hydrated component updates that could lose content when adding elements before existing content in a fragment
Prevent navigation and reloads from hanging when a nested
Framemarker moves outside a frame region while the DOM is being updated.Preserve client entry and frame state only for live boundaries with matching semantic identities, while replacing pending client entry SSR during reloads and releasing temporary response metadata after hydration or cancellation.
Prevent client-side document navigation and
Frameupdates from stalling after navigating between pages with differentFramelayouts. A frame's end marker could be reused as the start marker of an incoming frame, which left the frame's region bounds and instance pointing at the wrong nodes.Preserve resolved client-created frame content when its parent rerenders while the frame is still pending (see #11659).
Prevent document and frame reloads from dropping newly rendered sibling elements when client entries from the previous content are disposed.
Reload frames rendered within preserved client entries during ancestor frame reloads
Show complete destination server-rendered client entry content during document and frame reloads while replacement modules load, instead of retaining only positionally matched source content.
Fix top frame reloads using the previous URL after navigation targets a named frame.
- 0.4.x
0.4.0
1 Jul 2026Reading the note…
- 0.3.x
0.3.0
29 May 2026Reading the note…
- 0.2.x
0.2.0
20 May 2026Reading the note…
- 0.1.x
0.1.2
20 May 2026Reading the note…
0.1.1
30 Apr 2026Reading the note…
0.1.0
29 Apr 2026Reading the note…
- 0.0.x
0.0.0
29 Apr 2026Reading the note…