Skip to content

rtl:/ltr: variants silently dropped — lightningcss downcompiles :dir() to :lang() which is unhandled #453

Description

@Noitidart

Describe the bug

The rtl: and ltr: variants from Tailwind CSS v4 are silently dropped during compilation. No error is produced — the styles simply never apply. Related to nativewind/nativewind#1433 but traced to a different root cause in the react-native-css v5 compiler.

Root cause

  1. Tailwind v4 generates :dir(rtl) in the rtl: variant (confirmed in tailwindcss source)
  2. Lightningcss 1.30.1 downcompiles :dir(rtl) to :is(:lang(ae), :lang(ar), ...) at the parse level
  3. parseIsWhereComponents in selector-builder.ts has a case "dir" handler but no case "lang" handler
  4. :lang() hits default: { return null; } → returns null
  5. Inside :where(), [dir="rtl"] attribute selectors also return null (line ~397)
  6. ALL alternatives produce nothing → the entire rule is silently dropped

Key finding: Features.DirSelector does not help

Adding Features.DirSelector to lightningcss include list does NOT prevent the downcompile. Confirmed with a console.log verifying the flag IS loaded — the CSS output still contains :is(:lang(...)) instead of :dir(rtl). The downcompile happens at the parse level, before feature flags are consulted.

Proposed fix

Add a case "lang" handler to parseIsWhereComponents. Lightningcss expands :dir(rtl) into a fixed set of 19 RTL languages. Map these back to [=, "dir", "rtl"], leveraging the existing runtime case "dir" check against I18nManager.isRTL:

case "lang": {
  const RTL_LANGUAGES = new Set([
    "ae","ar","arc","bcc","bqi","ckb","dv","fa","glk",
    "he","ku","mzn","nqo","pnb","ps","sd","ug","ur","yi"
  ]);
  const direction = component.languages?.[0] && RTL_LANGUAGES.has(component.languages[0]) ? "rtl" : "ltr";
  queries ??= [{ specificity: [] }];
  queries.forEach(query => {
    getMediaQuery(query).push(["=", "dir", direction]);
  });
  return parseIsWhereComponents(type, selector, index + 1, queries);
}

Reproduction

Steps:

  1. npm install && npx expo run:ios
  2. Press "Force RTL" button
  3. Fully restart the app
  4. The rtl:bg-amber-400 class should make the background amber, but it stays blue

Expected behavior

rtl:bg-amber-400 should apply background-color: amber-400 when I18nManager.isRTL === true.

Workaround

Use patch-package with the patch from the fix branch.

Environment

  • react-native-css: 3.0.7
  • nativewind: 5.0.0-preview.4
  • react-native: 0.86.0
  • expo: 57

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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