Skip to content

feat(direction): auto-detect RTL from HTML document when no DirectionProvider is present #3830

@imohad

Description

@imohad

Problem

useDirection() currently falls back to 'ltr' when no DirectionProvider exists in the React tree:

// packages/react/direction/src/direction.tsx
function useDirection(localDir?: Direction) {
  const globalDir = React.useContext(DirectionContext);
  return localDir || globalDir || 'ltr'; // ← hardcoded fallback
}

This means every Radix component renders dir="ltr" on its DOM elements — ignoring <html dir="rtl"> entirely.

Real-world impact

We discovered this while contributing Arabic/RTL support to AFFiNE (65k+ GitHub stars). Even after correctly setting <html lang="ar" dir="rtl"> at the document level, every Radix dropdown, dialog, popover, and scroll area continued to render dir="ltr" — effectively overriding the document direction.

The workaround: manually wrap the entire app in DirectionProvider with a MutationObserver. This works, but it requires every application to implement this boilerplate. With 130M+ monthly downloads and Radix powering shadcn/ui (used by Vercel, Linear, Supabase), the RTL community repeats this fix across every project.

Proposed fix

function useDirection(localDir?: Direction) {
  const globalDir = React.useContext(DirectionContext);
  const documentDir =
    typeof document !== 'undefined'
      ? document.documentElement.dir === 'rtl' ? 'rtl' : 'ltr'
      : 'ltr';
  return localDir || globalDir || documentDir;
}

Why this is safe

  • No breaking changes: DirectionProvider still takes precedence — existing apps are unaffected
  • LTR apps unchanged: Most apps don't set <html dir="rtl">, so documentDir defaults to 'ltr'
  • SSR safe: typeof document !== 'undefined' guard handles server-side rendering
  • Dynamic: Works with runtime language switching (when dir changes on <html>)
  • Zero configuration: RTL apps work correctly without any wrapper

Who this helps

Arabic, Hebrew, Persian, and Urdu speakers — ~500 million people. Every app using Radix that sets <html dir="rtl"> currently has broken RTL layout for dropdowns, dialogs, and popovers.

Context

  • Validated during AFFiNE Arabic localization: PR #14646
  • shadcn added RTL support in January 2026 — this change would make it automatic for all Radix consumers
  • The fix is a single line change in one file

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions