ProComponents, templates & AI tooling
2.3k

v1.0.8

RTL layout support across components via provider isRTL config and LayoutDirectionScope, self-declared @source in the shipped stylesheet, Card.Header and TagGroup.List layout changes

July 31, 2026

HeroUI Native v1.0.8 adds right-to-left layout support across the library. Component styles move from physical left/right to Yoga logical properties, and the JS-driven logic Yoga cannot cover — gesture deltas, animation offsets, popover alignment — now resolves direction through a new isRTL provider config, with a LayoutDirectionScope escape hatch for subtrees. This release also makes heroui-native/styles declare its own @source, so class scanning no longer depends on a hardcoded node_modules path in your app.

Installation

Update to the latest version:

npm i heroui-native
pnpm add heroui-native
yarn add heroui-native
bun add heroui-native

Using AI assistants? Simply prompt "Hey Cursor, update HeroUI Native to the latest version" and your AI assistant will automatically compare versions and apply the necessary changes. Learn more about the HeroUI Native MCP Server.

Try It on Your Device

Don't have the HeroUI Native app yet? Download it below.

What's New

RTL Layout Support

Components now mirror correctly when your app runs in a right-to-left locale. Component CSS uses logical properties throughout (inset-inline-*, padding-inline-*, start/end) so Yoga handles positioning in both directions, and the behavior Yoga cannot express — gesture deltas, animation offsets, overlay alignment — reads the effective direction from the provider.

Telling components which direction they render in:

HeroUINativeProvider accepts config.isRTL, defaulting to I18nManager.isRTL:

import { HeroUINativeProvider } from 'heroui-native';
import type { HeroUINativeConfig } from 'heroui-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

const config: HeroUINativeConfig = {
  // Defaults to I18nManager.isRTL
  isRTL: true,
};

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <HeroUINativeProvider config={config}>
        {/* Your app content */}
      </HeroUINativeProvider>
    </GestureHandlerRootView>
  );
}

Because the default already follows I18nManager.isRTL, apps that rely on the native RTL flag need no changes. Set it explicitly when direction comes from somewhere else — for example a runtime locale switcher that changes direction without a reload.

Scoping a subtree to a different direction:

The new LayoutDirectionScope export overrides direction for the components beneath it, which is what you want for a demo or preview area that must stay left-to-right inside a right-to-left app. It covers only the JavaScript side, so pair it with Uniwind's LayoutDirection for the rtl: variants and a direction style for Yoga layout:

import { LayoutDirectionScope, PortalHost } from 'heroui-native';
import { LayoutDirection } from 'uniwind';
import { View } from 'react-native';

<LayoutDirection rtl={false}>
  <View className="flex-1" style={{ direction: 'ltr' }}>
    <LayoutDirectionScope isRTL={false}>
      <PreviewArea />
      <PortalHost name="preview" />
    </LayoutDirectionScope>
  </View>
</LayoutDirection>;

The scope drives the direction reads that happen in JavaScript: gesture inversion in Slider, animation offsets in Skeleton and SubMenu, and start/end alignment for Popover and Menu. Your own components can read the same value with the newly exported useIsRTL hook.

Portals escape the scope. Content rendered through a Portal mounts at the app root, so overlays fall back to the app-level direction. To keep them scoped, render a PortalHost with a custom name inside the scope — as above — and pass the matching hostName to the overlay.

Direction-aware behavior:

  • Slider: tap and drag deltas are inverted, and the fill and thumb anchor to logical edges
  • Skeleton: the shimmer sweeps along the reading direction
  • Switch: thumb travel is anchored on start
  • Tabs: indicator re-basing is keyed off the actual anchor swap rather than assuming a left anchor
  • Popover and Menu: start/end alignment resolves per direction
  • InputGroup: measured affix widths are applied as paddingStart/paddingEnd
  • SearchField, ListGroup, and SubMenu: default chevron and magnifier icons mirror

Text alignment:

Text-based parts across Card, Dialog, Menu, Select, Toast, Label, Description, FieldError, and others align to the leading edge in both directions. TextInput parts get an explicit rtl:text-right, since React Native resolves TextInput alignment physically rather than logically.

The example app now ships en, ar, and he catalogs via Lingui with a runtime locale switcher, plus an eslint-plugin-lingui guard against untranslated strings. The release was verified manually on iOS and Android across all three locales, including scoped left-to-right previews inside a right-to-left app shell.

Related PR: #459

Styling

The Shipped Stylesheet Declares Its Own @source

heroui-native/styles now points Tailwind at its own files:

@source "..";

Tailwind v4 resolves @source relative to the CSS file that contains it, so in the published package lib/module/styles/index.css resolves the scan path to lib/module — wherever the package manager installed it.

Previously every app had to hardcode a filesystem path into node_modules:

/* Before — the app had to know where the package lives */
@import 'heroui-native/styles';
@source './node_modules/heroui-native/lib';

/* After — the package declares its own scan path */
@import 'heroui-native/styles';

The two directives never behaved the same way. @import goes through package exports and finds the package anywhere; @source is a filesystem glob that only works if the package sits at that literal path. Those agree in a single-package app and disagree in a workspace, where the installer — not the project layout — decides which node_modules holds the package. Bun's isolated linker puts it in the app, its hoisted linker puts it at the workspace root, and pnpm and yarn split the same way depending on hoisting.

The failure was also silent: a missing @source directory is a no-op in Tailwind, with no warning or error. App-level classes kept working while classes used only inside HeroUI's shipped components disappeared, so Avatar lost its sizing, Switch rendered invisible, and Popover backgrounds went transparent.

Existing app-level @source lines keep working — they are simply redundant now and can be removed.

Related PR: #455

⚠️ Breaking Changes

Card.Header Aligns Children to the Leading Edge

.card__header now sets align-items: flex-start instead of inheriting the default stretch. Cross-axis flex-start follows layout direction, which is what makes header content sit on the leading edge in RTL — but it also changes LTR rendering: header children that previously stretched to the full header width now shrink-wrap to their content.

Migration:

The change lives in CSS, so items-stretch restores the previous behavior:

// Before — children stretched to the full header width
<Card.Header>
  <SomeFullWidthChild />
</Card.Header>

// After — restore stretching explicitly
<Card.Header className="items-stretch">
  <SomeFullWidthChild />
</Card.Header>

Related PR: #459

TagGroup.List Applies an Inline Layout Style

TagGroup.List now receives an inline style (width: '100%', alignItems: 'flex-start') merged ahead of the style you pass, which works around a Yoga wrap-measurement problem. Inline styles win over class-resolved ones, so className overrides for width or alignItems on this part no longer take effect.

Migration:

Move those two properties from className to style, which is merged after the internal values and therefore still wins:

// Before — className override applied
<TagGroup.List className="w-auto items-center" />

// After — use style for width and alignItems
<TagGroup.List style={{ width: 'auto', alignItems: 'center' }} />

Other className overrides on TagGroup are unaffected.

Related PR: #459

Bug Fixes

This release includes fixes for the following issues:

  • Issue #454: Fixed class scanning breaking in monorepo layouts. heroui-native/styles did not declare an @source for its own files, so apps had to hardcode a path into node_modules that only resolved under some package-manager layouts — and when it was wrong, Tailwind scanned nothing and failed silently, leaving HeroUI components rendering without their styles. The shipped stylesheet now declares its own scan path relative to itself.

Related PRs:

Internal Changes

This release also removes circular module dependencies: the Surface context is split into a leaf module, and shared Toast configuration is extracted into toast.base-types. Only internal import paths change — no public API is affected.

Related PR: #459

Updated Documentation

The following documentation pages have been updated to reflect the changes in this release:

  • Quick Start - The global.css setup no longer includes an @source line, with the path-based instructions kept for v1.0.7 and earlier
  • Provider - Documents the isRTL config option, the LayoutDirectionScope override, and the useIsRTL hook

Contributors

Thanks to everyone who contributed to this release!

本页目录