From eabc9cb50bf2316262474fd0e3381e7af3ec9e4e Mon Sep 17 00:00:00 2001
From: Evan Simpson <25159851+e-simpson@users.noreply.github.com>
Date: Sat, 24 Jan 2026 12:23:01 -0500
Subject: [PATCH 1/3] Implement outline class for new arch
---
docs/src/content/docs/reference/borders.md | 56 ++++++++++-
docs/src/content/docs/reference/outlines.md | 59 +++++++++++
src/index.ts | 1 +
src/parser/colors.ts | 32 +++++-
src/parser/index.ts | 3 +
src/parser/outline.test.ts | 57 +++++++++++
src/parser/outline.ts | 102 ++++++++++++++++++++
7 files changed, 303 insertions(+), 7 deletions(-)
create mode 100644 docs/src/content/docs/reference/outlines.md
create mode 100644 src/parser/outline.test.ts
create mode 100644 src/parser/outline.ts
diff --git a/docs/src/content/docs/reference/borders.md b/docs/src/content/docs/reference/borders.md
index a63627f..3aeabf6 100644
--- a/docs/src/content/docs/reference/borders.md
+++ b/docs/src/content/docs/reference/borders.md
@@ -117,10 +117,7 @@ Apply colors to individual border sides. See the [Colors reference](/react-nativ
### Combining Width and Color
```tsx
-
- // borderLeftWidth: 4
- // borderLeftColor: '#3B82F6'
-
+// borderLeftWidth: 4 // borderLeftColor: '#3B82F6'
```
### Example: Accent Border
@@ -168,3 +165,54 @@ Apply colors to individual border sides. See the [Colors reference](/react-nativ
- [Colors](/react-native-tailwind/reference/colors/) - Border color utilities
- [Shadows](/react-native-tailwind/reference/shadows/) - Shadow and elevation
+
+## Outline
+
+Utilities for controlling the outline style of an element.
+
+> **Note**: Outline support requires React Native 0.73+ (New Architecture) and setting the `outline` style property.
+
+### Outline Width
+
+```tsx
+ // outlineWidth: 1, outlineStyle: 'solid'
+ // outlineWidth: 0
+ // outlineWidth: 2
+ // outlineWidth: 4
+ // outlineWidth: 2
+ // outlineWidth: 0
+```
+
+### Outline Color
+
+```tsx
+ // outlineColor: '#3B82F6'
+ // outlineColor: '#ff0000'
+ // outlineColor: '#EF4444' (50% opacity)
+```
+
+### Outline Style
+
+```tsx
+ // outlineStyle: 'solid'
+ // outlineStyle: 'dashed'
+ // outlineStyle: 'dotted'
+```
+
+### Outline Offset
+
+Utilities for controlling the offset of an element's outline.
+
+```tsx
+ // outlineOffset: 0
+ // outlineOffset: 1
+ // outlineOffset: 2
+ // outlineOffset: 4
+ // outlineOffset: 3
+```
+
+### Example
+
+```tsx
+
+```
diff --git a/docs/src/content/docs/reference/outlines.md b/docs/src/content/docs/reference/outlines.md
new file mode 100644
index 0000000..7593e08
--- /dev/null
+++ b/docs/src/content/docs/reference/outlines.md
@@ -0,0 +1,59 @@
+---
+title: Outlines
+description: Outline width, style, and offset utilities
+---
+
+Utilities for controlling the outline style of an element.
+
+> **Note**: Outline support requires React Native 0.73+ (New Architecture) and setting the `outline` style property.
+
+## Outline Width
+
+```tsx
+ // outlineWidth: 1, outlineStyle: 'solid'
+ // outlineWidth: 0
+ // outlineWidth: 2
+ // outlineWidth: 4
+ // outlineWidth: 2
+ // outlineWidth: 0
+```
+
+## Outline Color
+
+```tsx
+ // outlineColor: '#3B82F6'
+ // outlineColor: '#ff0000'
+ // outlineColor: '#EF4444' (50% opacity)
+```
+
+## Outline Style
+
+```tsx
+ // outlineStyle: 'solid'
+ // outlineStyle: 'dashed'
+ // outlineStyle: 'dotted'
+```
+
+## Outline Offset
+
+Utilities for controlling the offset of an element's outline.
+
+```tsx
+ // outlineOffset: 0
+ // outlineOffset: 1
+ // outlineOffset: 2
+ // outlineOffset: 4
+ // outlineOffset: 3
+```
+
+## Example
+
+```tsx
+
+```
+
+## Related
+
+- [Borders](/react-native-tailwind/reference/borders/) - Border width, radius, and style utilities
+- [Colors](/react-native-tailwind/reference/colors/) - Color utilities
+- [Shadows](/react-native-tailwind/reference/shadows/) - Shadow and elevation
diff --git a/src/index.ts b/src/index.ts
index 515af19..0bd46f6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -25,6 +25,7 @@ export {
parseBorder,
parseColor,
parseLayout,
+ parseOutline,
parsePlaceholderClass,
parsePlaceholderClasses,
parseShadow,
diff --git a/src/parser/colors.ts b/src/parser/colors.ts
index 3966a00..b23e7ab 100644
--- a/src/parser/colors.ts
+++ b/src/parser/colors.ts
@@ -12,7 +12,10 @@ export { COLORS };
* Parse color classes (background, text, border)
* Supports opacity modifier: bg-blue-500/50, text-black/80, border-red-500/30
*/
-export function parseColor(cls: string, customColors?: Record): StyleObject | null {
+export function parseColor(
+ cls: string,
+ customColors?: Record
+): StyleObject | null {
// Helper to get color with custom override (custom colors take precedence)
const getColor = (key: string): string | undefined => {
return customColors?.[key] ?? COLORS[key];
@@ -32,7 +35,7 @@ export function parseColor(cls: string, customColors?: Record):
/* v8 ignore next 5 */
if (process.env.NODE_ENV !== "production") {
console.warn(
- `[react-native-tailwind] Invalid opacity value: ${opacity}. Opacity must be between 0 and 100.`,
+ `[react-native-tailwind] Invalid opacity value: ${opacity}. Opacity must be between 0 and 100.`
);
}
return null;
@@ -65,7 +68,7 @@ export function parseColor(cls: string, customColors?: Record):
/* v8 ignore next 5 */
if (process.env.NODE_ENV !== "production") {
console.warn(
- `[react-native-tailwind] Unsupported arbitrary color value: ${colorKey}. Only hex colors are supported (e.g., [#ff0000], [#f00], or [#ff0000aa]).`,
+ `[react-native-tailwind] Unsupported arbitrary color value: ${colorKey}. Only hex colors are supported (e.g., [#ff0000], [#f00], or [#ff0000aa]).`
);
}
return null;
@@ -116,6 +119,29 @@ export function parseColor(cls: string, customColors?: Record):
}
}
+ // Outline color: outline-blue-500, outline-blue-500/50, outline-[#ff0000]/80
+ if (
+ cls.startsWith("outline-") &&
+ !cls.match(/^outline-[0-9]/) &&
+ !cls.startsWith("outline-offset-")
+ ) {
+ const colorKey = cls.substring(8); // "outline-".length = 8
+
+ // Skip outline-style values
+ if (["solid", "dashed", "dotted", "none"].includes(colorKey)) {
+ return null;
+ }
+
+ // Skip arbitrary values that don't look like colors (e.g., outline-[3px] is width)
+ if (colorKey.startsWith("[") && !colorKey.startsWith("[#")) {
+ return null;
+ }
+ const color = parseColorWithOpacity(colorKey);
+ if (color) {
+ return { outlineColor: color };
+ }
+ }
+
// Directional border colors: border-t-red-500, border-l-blue-500/50, border-r-[#ff0000]
const dirBorderMatch = cls.match(/^border-([trblxy])-(.+)$/);
if (dirBorderMatch) {
diff --git a/src/parser/index.ts b/src/parser/index.ts
index ebc143a..8599c14 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -9,6 +9,7 @@ import { parseAspectRatio } from "./aspectRatio";
import { parseBorder } from "./borders";
import { parseColor } from "./colors";
import { parseLayout } from "./layout";
+import { parseOutline } from "./outline";
import { parseShadow } from "./shadows";
import { parseSizing } from "./sizing";
import { parseSpacing } from "./spacing";
@@ -56,6 +57,7 @@ export function parseClass(cls: string, customTheme?: CustomTheme): StyleObject
const parsers: Array<(cls: string) => StyleObject | null> = [
(cls: string) => parseSpacing(cls, customTheme?.spacing),
(cls: string) => parseBorder(cls, customTheme?.colors),
+ (cls: string) => parseOutline(cls, customTheme?.colors),
(cls: string) => parseColor(cls, customTheme?.colors),
(cls: string) => parseLayout(cls, customTheme?.spacing),
(cls: string) => parseTypography(cls, customTheme?.fontFamily, customTheme?.fontSize),
@@ -86,6 +88,7 @@ export { parseAspectRatio } from "./aspectRatio";
export { parseBorder } from "./borders";
export { parseColor } from "./colors";
export { parseLayout } from "./layout";
+export { parseOutline } from "./outline";
export { parsePlaceholderClass, parsePlaceholderClasses } from "./placeholder";
export { parseShadow } from "./shadows";
export { parseSizing } from "./sizing";
diff --git a/src/parser/outline.test.ts b/src/parser/outline.test.ts
new file mode 100644
index 0000000..56e396d
--- /dev/null
+++ b/src/parser/outline.test.ts
@@ -0,0 +1,57 @@
+import { describe, expect, it } from "vitest";
+import { parseOutline } from "./outline";
+
+describe("parseOutline", () => {
+ it("should parse outline shorthand", () => {
+ expect(parseOutline("outline")).toEqual({
+ outlineWidth: 1,
+ outlineStyle: "solid",
+ });
+ });
+
+ it("should parse outline-none", () => {
+ expect(parseOutline("outline-none")).toEqual({ outlineWidth: 0 });
+ });
+
+ it("should parse outline width with preset values", () => {
+ expect(parseOutline("outline-0")).toEqual({ outlineWidth: 0 });
+ expect(parseOutline("outline-2")).toEqual({ outlineWidth: 2 });
+ expect(parseOutline("outline-4")).toEqual({ outlineWidth: 4 });
+ expect(parseOutline("outline-8")).toEqual({ outlineWidth: 8 });
+ });
+
+ it("should parse outline width with arbitrary values", () => {
+ expect(parseOutline("outline-[5px]")).toEqual({ outlineWidth: 5 });
+ expect(parseOutline("outline-[10]")).toEqual({ outlineWidth: 10 });
+ });
+
+ it("should parse outline style", () => {
+ expect(parseOutline("outline-solid")).toEqual({ outlineStyle: "solid" });
+ expect(parseOutline("outline-dashed")).toEqual({ outlineStyle: "dashed" });
+ expect(parseOutline("outline-dotted")).toEqual({ outlineStyle: "dotted" });
+ });
+
+ it("should parse outline offset with preset values", () => {
+ expect(parseOutline("outline-offset-0")).toEqual({ outlineOffset: 0 });
+ expect(parseOutline("outline-offset-2")).toEqual({ outlineOffset: 2 });
+ expect(parseOutline("outline-offset-4")).toEqual({ outlineOffset: 4 });
+ expect(parseOutline("outline-offset-8")).toEqual({ outlineOffset: 8 });
+ });
+
+ it("should parse outline offset with arbitrary values", () => {
+ expect(parseOutline("outline-offset-[3px]")).toEqual({ outlineOffset: 3 });
+ expect(parseOutline("outline-offset-[5]")).toEqual({ outlineOffset: 5 });
+ });
+
+ it("should return null for invalid outline values", () => {
+ expect(parseOutline("outline-invalid")).toBeNull();
+ expect(parseOutline("outline-3")).toBeNull(); // Not in scale
+ expect(parseOutline("outline-offset-3")).toBeNull(); // Not in scale
+ expect(parseOutline("outline-[5rem]")).toBeNull(); // Unsupported unit
+ });
+
+ it("should return null for outline colors (handled by parseColor)", () => {
+ expect(parseOutline("outline-red-500")).toBeNull();
+ expect(parseOutline("outline-[#ff0000]")).toBeNull();
+ });
+});
diff --git a/src/parser/outline.ts b/src/parser/outline.ts
new file mode 100644
index 0000000..2ec21c7
--- /dev/null
+++ b/src/parser/outline.ts
@@ -0,0 +1,102 @@
+/**
+ * Outline utilities (outline width, style, offset)
+ */
+
+import type { StyleObject } from "../types";
+import { BORDER_WIDTH_SCALE } from "./borders";
+
+/**
+ * Parse arbitrary outline width/offset value: [8px], [4]
+ * Returns number for px values, null for unsupported formats
+ */
+function parseArbitraryOutlineValue(value: string): number | null {
+ // Match: [8px] or [8] (pixels only)
+ const pxMatch = value.match(/^\[(\d+)(?:px)?\]$/);
+ if (pxMatch) {
+ return parseInt(pxMatch[1], 10);
+ }
+
+ // Warn about unsupported formats
+ if (value.startsWith("[") && value.endsWith("]")) {
+ /* v8 ignore next 5 */
+ if (process.env.NODE_ENV !== "production") {
+ console.warn(
+ `[react-native-tailwind] Unsupported arbitrary outline value: ${value}. Only px values are supported (e.g., [8px] or [8]).`,
+ );
+ }
+ return null;
+ }
+
+ return null;
+}
+
+/**
+ * Parse outline classes
+ * @param cls - The class name to parse
+ * @param customColors - Optional custom colors (passed to parseColor for pattern detection)
+ */
+export function parseOutline(cls: string, customColors?: Record): StyleObject | null {
+ // Shorthand: outline (width: 1, style: solid)
+ if (cls === "outline") {
+ return { outlineWidth: 1, outlineStyle: "solid" };
+ }
+
+ // Outline none
+ if (cls === "outline-none") {
+ return { outlineWidth: 0 };
+ }
+
+ // Outline style
+ if (cls === "outline-solid") return { outlineStyle: "solid" };
+ if (cls === "outline-dotted") return { outlineStyle: "dotted" };
+ if (cls === "outline-dashed") return { outlineStyle: "dashed" };
+
+ // Outline offset: outline-offset-2, outline-offset-[3px]
+ if (cls.startsWith("outline-offset-")) {
+ const valueStr = cls.substring(15); // "outline-offset-".length = 15
+
+ // Try arbitrary value first
+ if (valueStr.startsWith("[")) {
+ const arbitraryValue = parseArbitraryOutlineValue(valueStr);
+ if (arbitraryValue !== null) {
+ return { outlineOffset: arbitraryValue };
+ }
+ return null;
+ }
+
+ // Try preset scale (reuse border width scale for consistency with default Tailwind)
+ const scaleValue = BORDER_WIDTH_SCALE[valueStr];
+ if (scaleValue !== undefined) {
+ return { outlineOffset: scaleValue };
+ }
+
+ return null;
+ }
+
+ // Outline width: outline-0, outline-2, outline-[5px]
+ // Must handle potential collision with outline-red-500 (colors)
+ // Logic: if it matches width pattern, return width. If it looks like color, return null (let parseColor handle it)
+
+ const widthMatch = cls.match(/^outline-(\d+)$/);
+ if (widthMatch) {
+ const value = BORDER_WIDTH_SCALE[widthMatch[1]];
+ if (value !== undefined) {
+ return { outlineWidth: value };
+ }
+ }
+
+ const arbMatch = cls.match(/^outline-(\[.+\])$/);
+ if (arbMatch) {
+ // Check if it's a color first? No, colors usually look like [#...] or [rgb(...)]
+ // parseArbitraryOutlineValue only accepts [123] or [123px]
+ // If it fails, it might be a color, so we return null
+ const arbitraryValue = parseArbitraryOutlineValue(arbMatch[1]);
+ if (arbitraryValue !== null) {
+ return { outlineWidth: arbitraryValue };
+ }
+ return null;
+ }
+
+ // If it's outline-{color}, we return null here so parseColor (called later in index.ts)
+ return null;
+}
From fed09f8ed9238b6c66963127ec1fa7d8db0436b3 Mon Sep 17 00:00:00 2001
From: Olivier Louvignes
Date: Sun, 25 Jan 2026 16:33:01 +0100
Subject: [PATCH 2/3] refactor: remove unused customColors parameter from
parseOutline
---
src/parser/index.ts | 2 +-
src/parser/outline.ts | 5 ++---
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/parser/index.ts b/src/parser/index.ts
index 8599c14..0e89fcc 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -57,7 +57,7 @@ export function parseClass(cls: string, customTheme?: CustomTheme): StyleObject
const parsers: Array<(cls: string) => StyleObject | null> = [
(cls: string) => parseSpacing(cls, customTheme?.spacing),
(cls: string) => parseBorder(cls, customTheme?.colors),
- (cls: string) => parseOutline(cls, customTheme?.colors),
+ parseOutline,
(cls: string) => parseColor(cls, customTheme?.colors),
(cls: string) => parseLayout(cls, customTheme?.spacing),
(cls: string) => parseTypography(cls, customTheme?.fontFamily, customTheme?.fontSize),
diff --git a/src/parser/outline.ts b/src/parser/outline.ts
index 2ec21c7..d1e04e7 100644
--- a/src/parser/outline.ts
+++ b/src/parser/outline.ts
@@ -33,9 +33,8 @@ function parseArbitraryOutlineValue(value: string): number | null {
/**
* Parse outline classes
* @param cls - The class name to parse
- * @param customColors - Optional custom colors (passed to parseColor for pattern detection)
*/
-export function parseOutline(cls: string, customColors?: Record): StyleObject | null {
+export function parseOutline(cls: string): StyleObject | null {
// Shorthand: outline (width: 1, style: solid)
if (cls === "outline") {
return { outlineWidth: 1, outlineStyle: "solid" };
@@ -97,6 +96,6 @@ export function parseOutline(cls: string, customColors?: Record)
return null;
}
- // If it's outline-{color}, we return null here so parseColor (called later in index.ts)
+ // If it's outline-{color}, return null so parseColor (called later in index.ts) handles it
return null;
}
From 8433ac66465f3b9ab0e22f7e9b7a5e219d485a5e Mon Sep 17 00:00:00 2001
From: Olivier Louvignes
Date: Sun, 25 Jan 2026 16:33:08 +0100
Subject: [PATCH 3/3] docs: add outlines to sidebar and clean up borders page
- Add outlines entry to sidebar navigation
- Fix formatting regression in borders.md example
- Remove duplicate outline documentation from borders.md
- Add link to dedicated outlines page
---
docs/astro.config.mjs | 1 +
docs/src/content/docs/reference/borders.md | 57 ++--------------------
2 files changed, 6 insertions(+), 52 deletions(-)
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 5df770e..d6e7a0b 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -55,6 +55,7 @@ export default defineConfig({
{ label: "Colors", slug: "reference/colors" },
{ label: "Typography", slug: "reference/typography" },
{ label: "Borders", slug: "reference/borders" },
+ { label: "Outlines", slug: "reference/outlines" },
{ label: "Shadows & Elevation", slug: "reference/shadows" },
{ label: "Aspect Ratio", slug: "reference/aspect-ratio" },
{ label: "Transforms", slug: "reference/transforms" },
diff --git a/docs/src/content/docs/reference/borders.md b/docs/src/content/docs/reference/borders.md
index 3aeabf6..9362a1c 100644
--- a/docs/src/content/docs/reference/borders.md
+++ b/docs/src/content/docs/reference/borders.md
@@ -117,7 +117,10 @@ Apply colors to individual border sides. See the [Colors reference](/react-nativ
### Combining Width and Color
```tsx
-// borderLeftWidth: 4 // borderLeftColor: '#3B82F6'
+
+ // borderLeftWidth: 4
+ // borderLeftColor: '#3B82F6'
+
```
### Example: Accent Border
@@ -164,55 +167,5 @@ Apply colors to individual border sides. See the [Colors reference](/react-nativ
## Related
- [Colors](/react-native-tailwind/reference/colors/) - Border color utilities
+- [Outlines](/react-native-tailwind/reference/outlines/) - Outline width, style, and offset utilities
- [Shadows](/react-native-tailwind/reference/shadows/) - Shadow and elevation
-
-## Outline
-
-Utilities for controlling the outline style of an element.
-
-> **Note**: Outline support requires React Native 0.73+ (New Architecture) and setting the `outline` style property.
-
-### Outline Width
-
-```tsx
- // outlineWidth: 1, outlineStyle: 'solid'
- // outlineWidth: 0
- // outlineWidth: 2
- // outlineWidth: 4
- // outlineWidth: 2
- // outlineWidth: 0
-```
-
-### Outline Color
-
-```tsx
- // outlineColor: '#3B82F6'
- // outlineColor: '#ff0000'
- // outlineColor: '#EF4444' (50% opacity)
-```
-
-### Outline Style
-
-```tsx
- // outlineStyle: 'solid'
- // outlineStyle: 'dashed'
- // outlineStyle: 'dotted'
-```
-
-### Outline Offset
-
-Utilities for controlling the offset of an element's outline.
-
-```tsx
- // outlineOffset: 0
- // outlineOffset: 1
- // outlineOffset: 2
- // outlineOffset: 4
- // outlineOffset: 3
-```
-
-### Example
-
-```tsx
-
-```