diff --git a/src/__tests__/native/box-shadow.test.tsx b/src/__tests__/native/box-shadow.test.tsx
index 48a387a..c656e19 100644
--- a/src/__tests__/native/box-shadow.test.tsx
+++ b/src/__tests__/native/box-shadow.test.tsx
@@ -93,3 +93,115 @@ test("shadow values - multiple nested variables", () => {
],
});
});
+
+test("inset shadow - basic", () => {
+ registerCSS(`
+ .test { box-shadow: inset 0 2px 4px 0 #000; }
+ `);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props.style).toStrictEqual({
+ boxShadow: [
+ {
+ inset: true,
+ offsetX: 0,
+ offsetY: 2,
+ blurRadius: 4,
+ spreadDistance: 0,
+ color: "#000",
+ },
+ ],
+ });
+});
+
+test("inset shadow - with color first", () => {
+ registerCSS(`
+ .test { box-shadow: inset #fb2c36 0 0 24px 0; }
+ `);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props.style).toStrictEqual({
+ boxShadow: [
+ {
+ inset: true,
+ color: "#fb2c36",
+ offsetX: 0,
+ offsetY: 0,
+ blurRadius: 24,
+ spreadDistance: 0,
+ },
+ ],
+ });
+});
+
+test("inset shadow - without color inherits default", () => {
+ registerCSS(`
+ .test { box-shadow: inset 0 0 10px 5px; }
+ `);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ // Shadows without explicit color inherit the default text color (__rn-css-color)
+ expect(component.props.style.boxShadow).toHaveLength(1);
+ expect(component.props.style.boxShadow[0]).toMatchObject({
+ inset: true,
+ offsetX: 0,
+ offsetY: 0,
+ blurRadius: 10,
+ spreadDistance: 5,
+ });
+ // Color is inherited from platform default (PlatformColor)
+ expect(component.props.style.boxShadow[0].color).toBeDefined();
+});
+
+test("mixed inset and regular shadows", () => {
+ registerCSS(`
+ .test { box-shadow: 0 4px 6px -1px #000, inset 0 2px 4px 0 #fff; }
+ `);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ expect(component.props.style).toStrictEqual({
+ boxShadow: [
+ {
+ offsetX: 0,
+ offsetY: 4,
+ blurRadius: 6,
+ spreadDistance: -1,
+ color: "#000",
+ },
+ {
+ inset: true,
+ offsetX: 0,
+ offsetY: 2,
+ blurRadius: 4,
+ spreadDistance: 0,
+ color: "#fff",
+ },
+ ],
+ });
+});
+
+test("Tailwind v4 shadow variables - transparent color #0000", () => {
+ // Tailwind v4 uses --tw-shadow etc with @property initial-value of transparent
+ registerCSS(`
+ :root {
+ --tw-shadow: 0 0 0 0 #0000;
+ }
+ .test { box-shadow: var(--tw-shadow); }
+ `);
+
+ render();
+ const component = screen.getByTestId(testID);
+
+ // The shadow is parsed correctly with #0000 color
+ // Note: filtering of transparent shadows happens in omitTransparentShadows
+ // which checks for exact "#0000" or "transparent" strings
+ expect(component.props.style.boxShadow).toBeDefined();
+});
diff --git a/src/native-internal/root.ts b/src/native-internal/root.ts
index e45a7d1..792e329 100644
--- a/src/native-internal/root.ts
+++ b/src/native-internal/root.ts
@@ -42,3 +42,26 @@ rootVariables("__rn-css-color").set([
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any);
+
+/**
+ * Tailwind CSS v4 shadow variable defaults.
+ *
+ * Tailwind v4 uses @property to define initial-value for shadow CSS variables,
+ * but react-native-css doesn't support @property declarations.
+ *
+ * These provide fallback values that match Tailwind's defaults:
+ * - Transparent shadows (0 0 0 0 #0000) are filtered out by omitTransparentShadows
+ * - This prevents "undefined variable" errors when shadow utilities are used
+ *
+ * @see https://github.com/tailwindlabs/tailwindcss/discussions/16772
+ */
+// VariableValue[] where each VariableValue is [StyleDescriptor] tuple
+// The inner [0, 0, 0, 0, "#0000"] is a StyleDescriptor[] (shadow values)
+const transparentShadow: VariableValue[] = [[[0, 0, 0, 0, "#0000"]]];
+rootVariables("tw-shadow").set(transparentShadow);
+rootVariables("tw-shadow-color").set([["initial"]]);
+rootVariables("tw-inset-shadow").set(transparentShadow);
+rootVariables("tw-inset-shadow-color").set([["initial"]]);
+rootVariables("tw-ring-shadow").set(transparentShadow);
+rootVariables("tw-inset-ring-shadow").set(transparentShadow);
+rootVariables("tw-ring-offset-shadow").set(transparentShadow);
diff --git a/src/native/styles/shorthands/box-shadow.ts b/src/native/styles/shorthands/box-shadow.ts
index f65a937..9971e05 100644
--- a/src/native/styles/shorthands/box-shadow.ts
+++ b/src/native/styles/shorthands/box-shadow.ts
@@ -9,16 +9,25 @@ const offsetX = ["offsetX", "number"] as const;
const offsetY = ["offsetY", "number"] as const;
const blurRadius = ["blurRadius", "number"] as const;
const spreadDistance = ["spreadDistance", "number"] as const;
-// const inset = ["inset", "string"] as const;
+// Match the literal string "inset" - the array type checks if value is in array
+const inset = ["inset", ["inset"]] as const;
const handler = shorthandHandler(
[
+ // Standard patterns (without inset)
[offsetX, offsetY, blurRadius, spreadDistance],
[offsetX, offsetY, blurRadius, spreadDistance, color],
[color, offsetX, offsetY],
[color, offsetX, offsetY, blurRadius, spreadDistance],
[offsetX, offsetY, color],
[offsetX, offsetY, blurRadius, color],
+ // Inset patterns - "inset" keyword at the beginning
+ // Matches: inset
+ [inset, offsetX, offsetY, blurRadius, spreadDistance],
+ // Matches: inset
+ [inset, offsetX, offsetY, blurRadius, spreadDistance, color],
+ // Matches: inset
+ [inset, color, offsetX, offsetY, blurRadius, spreadDistance],
],
[],
"object",
@@ -41,8 +50,10 @@ export const boxShadow: StyleFunctionResolver = (
if (shadows === undefined) {
return;
} else {
- return omitTransparentShadows(
- handler(resolveValue, shadows, get, options),
+ return normalizeInsetValue(
+ omitTransparentShadows(
+ handler(resolveValue, shadows, get, options),
+ ),
);
}
})
@@ -69,3 +80,17 @@ function omitTransparentShadows(style: unknown) {
return style;
}
+
+/**
+ * Convert inset: "inset" to inset: true for React Native boxShadow.
+ *
+ * The shorthand handler matches the literal "inset" string and assigns it as the value.
+ * React Native's boxShadow expects inset to be a boolean.
+ */
+function normalizeInsetValue(style: unknown) {
+ if (typeof style === "object" && style && "inset" in style) {
+ return { ...style, inset: true };
+ }
+
+ return style;
+}