Skip to content

fix: emit React Native border-start/end props for border-inline styles#379

Open
danstepanov wants to merge 1 commit into
mainfrom
fix-logical-border-props
Open

fix: emit React Native border-start/end props for border-inline styles#379
danstepanov wants to merge 1 commit into
mainfrom
fix-logical-border-props

Conversation

@danstepanov

Copy link
Copy Markdown
Member

Closes #378, follow up to nativewind/nativewind#1737.

What

border-inline-start-color, border-inline-end-color, and the width and shorthand variants compiled to props like borderInlineStartColor that React Native does not have, so every border-s-* and border-e-* utility (and border-x-, which Tailwind v4 also emits as border-inline-) was a silent no-op on native.

  • Renames the inline logical border color and width properties to RN's RTL aware border-start-* and border-end-* props, in both the parsed path and the unparsed path that var() based colors take (the original Custom colors on border-s-* and border-e-* don't work nativewind#1737 report).
  • Fixes the border-inline, border-inline-start, and border-inline-end shorthands to emit those props, and fixes border-inline-width which previously only read the start value.
  • Drops per-side border styles since RN only has a uniform borderStyle. solid is dropped silently because it matches the native default; anything else records a compiler warning.
  • Dev builds now print compiler warnings from the metro transformer, so declarations without a native equivalent are visible while building instead of being collected and discarded.

Tests

  • New src/tests/compiler/logical-borders.test.ts covering colors (literal and var()), widths, shorthands, and the style drop and warning behavior.
  • Updated borders.test.tsx expectations, which previously locked in the invalid borderInline* prop names.
  • Full suite, typecheck, and lint pass locally. The three babel suites fail identically on clean main (missing --experimental-vm-modules in this environment) and are unrelated.

border-inline-start-color and friends compiled to props like
borderInlineStartColor that React Native does not support, so every
border-s-* and border-e-* utility was a silent no-op on native. Rename
them to the RTL-aware border-start-* and border-end-* props in both the
parsed and unparsed paths, fix the border-inline shorthands, and drop
per-side border styles (with a compiler warning when the style is not
solid, since solid matches the native default).

Dev builds now print compiler warnings from the metro transformer so
declarations without a native equivalent are visible while building.

Closes #378
@danstepanov danstepanov added the v5 stability Tracking v5 stability for @latest release label Jul 17, 2026
@danstepanov danstepanov added deferred (post-5.0) Not required for the 5.0 latest release; revisit after and removed v5 stability Tracking v5 stability for @latest release labels Jul 17, 2026

@YevheniiKotyrlo YevheniiKotyrlo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirming this from production (Expo SDK 55 · RN 0.83.6 · Tailwind v4) — border-s-* / border-e-* are invisible on native here (calendar column rules + a tree-view indent guide). Thanks for tackling it at the parser layer.

I built fix-logical-border-props locally and compiled the logical-border matrix through it. Longhands and literal shorthands are fixed. One gap the new logical-borders.test.ts doesn't cover: var()-valued border-inline shorthands still emit borderInline*.

Compiled through this branch (compile('.x { … }').stylesheet().s):

declaration this branch emits
border-inline-start-color: hsl(var(--p)) — longhand borderStartColor
border-inline-color: red blue — literal shorthand borderStartColor + borderEndColor
border-inline-color: hsl(var(--p))var shorthand borderInlineColor
border-inline-width: var(--w)var shorthand borderInlineWidth

A var() routes the shorthand to parseUnparsedDeclaration, where propertyRename maps only the longhands and the parseBorderInline* parsers never run — so border-inline-color / -width fall through unrenamed. Not hypothetical: Tailwind v4's border-x-<token> emits exactly border-inline-color: var(...) (v4 theme colors are CSS vars), so a themed border-x-primary is still a native no-op after this PR.

I have a fix ready on top of this branch. propertyRename can't do it (a shorthand needs a 1→2 expansion, not a 1→1 rename), so it goes in the same parseUnparsedDeclaration, mirroring your parseBorderInline* on the parsed path. The two broken cases now expand to borderStart/End*, and the logical-borders suite is 15/15 (your 13 + 2 new).

declarations.ts diff + the 2 tests
+// Logical-border SHORTHANDS have no RN equivalent either; when var()-valued
+// they reach the unparsed path (the parsed parseBorderInline* never run) and
+// propertyRename only maps the longhands. Expand each to its RTL-aware
+// start/end props, sharing the runtime value.
+const inlineShorthandExpansion: Record<string, string[]> = {
+  "border-inline-color": ["border-start-color", "border-end-color"],
+  "border-inline-width": ["border-start-width", "border-end-width"],
+};

 // …inside parseUnparsedDeclaration, right after the propertyRename block:
+  const shorthandExpansion = inlineShorthandExpansion[property];
+  if (shorthandExpansion) {
+    const value = parseUnparsed(declaration.value.value, builder, property);
+    for (const target of shorthandExpansion) {
+      builder.descriptorProperty = target;
+      builder.addDescriptor(target, value);
+    }
+    return;
+  }
test("border-inline-color with var()", () => {
  expect(getRule("border-inline-color: hsl(var(--primary));").rule).toStrictEqual([
    { s: [1, 1], dv: 1, d: [
      [[{}, "hsl", [{}, "var", "primary", 1]], "borderStartColor", 1],
      [[{}, "hsl", [{}, "var", "primary", 1]], "borderEndColor", 1],
    ] },
  ]);
});
test("border-inline-width with var()", () => {
  expect(getRule("border-inline-width: var(--w);").rule).toStrictEqual([
    { s: [1, 1], dv: 1, d: [
      [[{}, "var", "w", 1], "borderStartWidth", 1],
      [[{}, "var", "w", 1], "borderEndWidth", 1],
    ] },
  ]);
});

Happy to open this as a PR into fix-logical-border-props, or you can grab the diff — whichever you prefer. One rarer edge is deliberately left out: the full border-inline: 2px solid var() shorthand (still emits borderInline; it needs runtime shorthand splitting, and Tailwind never emits it) — a small follow-up if you want it.

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

Labels

deferred (post-5.0) Not required for the 5.0 latest release; revisit after

Projects

None yet

Development

Successfully merging this pull request may close these issues.

border-s-* and border-e-* compile to style props React Native does not support

2 participants