Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 8 min read

React Native Linear Gradient: Setup, Angles, and Gotchas

Gradients are the single most common thing designers hand over that React Native has no style for. There is no background: linear-gradient(...) in the style object you have been using, the Tailwind class you reach for silently does nothing, and the first package search returns two libraries with nearly identical names. This guide covers which one to install, the four gotchas that eat an afternoon — the TypeScript error, Android clipping, angles, and gradient text — and when a gradient is costing you performance.

Four ways to draw a gradient

ApproachInstallExpo GoBest forNote
expo-linear-gradientnpx expo install✅ YesAny Expo project — the defaultstart/end vectors, no angle prop
react-native-linear-gradientnpm + pod install❌ Dev buildBare RN CLI projectsHas useAngle / angle in degrees
backgroundImage styleBuilt in (newer RN)⚠️ Version-gatedSimple backgrounds, no extra depCheck your RN version first
react-native-svgnpx expo install✅ YesGradients inside shapes and pathsOverkill for a plain background

The decision is almost always made for you by your project type. If you used create-expo-app, install expo-linear-gradient and stop reading this section. The community package is for bare React Native CLI projects, and the two are not interchangeable — installing both is a common accident that produces confusing native build errors.

The setup, complete

npx expo install expo-linear-gradient

Use expo install rather than npm install so the version matches your SDK. There is no config plugin and nothing to add to app.json — it works in Expo Go immediately.

import { LinearGradient } from 'expo-linear-gradient';
import { StyleSheet, Text } from 'react-native';

export default function UpgradeCard() {
  return (
    <LinearGradient
      colors={['#fb923c', '#ea580c']}
      start={{ x: 0, y: 0 }}   // top-left
      end={{ x: 1, y: 1 }}     // bottom-right
      style={styles.card}
    >
      <Text style={styles.title}>Go Pro</Text>
      <Text style={styles.sub}>Unlimited projects, priority builds</Text>
    </LinearGradient>
  );
}

const styles = StyleSheet.create({
  // borderRadius belongs HERE, not on a wrapper View
  card: { borderRadius: 20, padding: 24, gap: 6 },
  title: { color: '#fff', fontSize: 22, fontWeight: '800' },
  sub: { color: 'rgba(255,255,255,0.85)', fontSize: 14 },
});

LinearGradient is a View with a painted background, so it accepts every layout style you already know and renders children on top of the gradient. That is the whole mental model: you are not applying a gradient to a box, you are using a box that happens to be a gradient.

Four gotchas, in the order you will hit them

1. The TypeScript error about tuples

Recent versions type colors as a tuple requiring at least two entries, so passing a plain string[] — very common when the colours come from a theme object — fails to compile with a message about readonly [ColorValue, ColorValue, ...ColorValue[]]. The fix is to preserve the tuple shape:

// ❌ theme.brandGradient is string[] — widened, won't compile
<LinearGradient colors={theme.brandGradient} />

// ✅ declare it as a tuple at the source
export const theme = {
  brandGradient: ['#fb923c', '#ea580c'] as const,
};

// ✅ or assert at the call site
<LinearGradient colors={theme.brandGradient as [string, string]} />

2. Rounded corners leaking on Android

If you wrap the gradient in a rounded View, Android will happily paint the gradient’s square corners over it while iOS looks correct — which is why this ships to production and gets reported a week later. Put borderRadius on the LinearGradient itself. If a wrapper is unavoidable because it owns the shadow, add overflow: 'hidden' to it.

3. Angles are relative to the box

start and end are fractions of the element, not screen coordinates, so {x:0,y:0} → {x:1,y:1} is corner-to-corner rather than a true 45°. On a wide, short banner that reads as almost horizontal. The useful presets:

// vertical (the default)
start={{ x: 0, y: 0 }} end={{ x: 0, y: 1 }}

// horizontal
start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }}

// corner to corner
start={{ x: 0, y: 0 }} end={{ x: 1, y: 1 }}

// three stops with explicit positions — locations must match colors length
colors={['#1c1c1c', '#2a1a10', '#fb923c']}
locations={[0, 0.6, 1]}

A mismatched locations array — three colours, two stops — does not throw. It renders something slightly wrong and you spend twenty minutes tuning hex values that were never the problem.

4. Gradient text needs a mask

Text has color, and color takes one value. To fill glyphs with a gradient you draw the gradient and cut it to the shape of the text:

npx expo install @react-native-masked-view/masked-view
import MaskedView from '@react-native-masked-view/masked-view';
import { LinearGradient } from 'expo-linear-gradient';
import { Text } from 'react-native';

export function GradientHeading({ children }) {
  return (
    <MaskedView maskElement={<Text style={s.text}>{children}</Text>}>
      <LinearGradient
        colors={['#fb923c', '#f43f5e']}
        start={{ x: 0, y: 0 }}
        end={{ x: 1, y: 0 }}
      >
        {/* invisible copy sets the size the gradient must fill */}
        <Text style={[s.text, { opacity: 0 }]}>{children}</Text>
      </LinearGradient>
    </MaskedView>
  );
}

Two patterns worth stealing

A fade over an image, so white text stays legible on any photo. This is the gradient that earns its keep in nearly every app:

<View>
  <Image source={{ uri: cover }} style={{ height: 220 }} />
  <LinearGradient
    colors={['transparent', 'rgba(0,0,0,0.85)']}
    style={StyleSheet.absoluteFillObject}
    pointerEvents="none"
  />
  <Text style={s.overlayTitle}>{title}</Text>
</View>

pointerEvents="none"matters — without it the overlay swallows taps meant for the card underneath, which shows up as “the top half of my list item is not pressable.”

Animating between gradients. Changing colors on every frame re-renders the native view and stutters. Stack two gradients and cross-fade instead:

const progress = useSharedValue(0);           // 0 → 1
const topStyle = useAnimatedStyle(() => ({ opacity: progress.value }));

<View>
  <LinearGradient colors={COOL} style={StyleSheet.absoluteFillObject} />
  <Animated.View style={[StyleSheet.absoluteFillObject, topStyle]}>
    <LinearGradient colors={WARM} style={StyleSheet.absoluteFillObject} />
  </Animated.View>
</View>

Use fewer of them than the mockup has

The technical problems above are all solvable in an afternoon. The design problem is not: gradients read as “generated” the moment every card, every button, and every header has one. The apps that look expensive use a single gradient as an accent — one primary button, or one hero — against otherwise flat surfaces. If your screen has three gradients, two of them are noise.

You can test that quickly rather than argue about it. Describe the screen — “a paywall with one gradient upgrade button and flat feature rows” — and ShipNative generates it as real React Native running on your phone, gradient package and all. Look at it at arm’s length, then decide how many you actually want. See also dark mode — every gradient you add is a second pair of colours to maintain.

Frequently Asked Questions

Should I use expo-linear-gradient or react-native-linear-gradient?

If your project uses Expo — which includes every project created with create-expo-app — use expo-linear-gradient. It installs with expo install, matches your SDK, and works in Expo Go. Use react-native-linear-gradient only in a bare React Native CLI project without Expo modules installed. The rendered result is the same; the two differ in packaging and in a handful of props.

Why does my gradient ignore borderRadius on Android?

A gradient is a View, and on Android a child View can paint past a rounded parent unless the parent clips. Put the borderRadius on the LinearGradient itself rather than on a wrapper, and if you need a wrapper, add overflow: "hidden" to it. On iOS the wrapper alone usually looks fine, which is why the bug tends to be reported as Android-only.

Can I use Tailwind or NativeWind gradient classes in React Native?

Classes like bg-gradient-to-r do not work, because they compile to CSS background-image and React Native has historically had no such style. Use the LinearGradient component instead and style it with className for everything except the gradient itself. Newer React Native versions do add a backgroundImage style that accepts linear-gradient() strings, but check that your version supports it before relying on it.

How do I make a gradient at a specific angle, like 45 degrees?

expo-linear-gradient takes start and end vectors in the 0–1 coordinate space of the box, so a 45-degree diagonal is start {x:0,y:0} to end {x:1,y:1}. react-native-linear-gradient additionally offers useAngle with an angle prop in degrees. Note the vectors are relative to the box, so a diagonal across a wide, short box is not a visual 45 degrees.

How do I apply a gradient to text?

Text cannot take a gradient fill directly. Render the gradient and mask it with the text using @react-native-masked-view/masked-view: the masked view draws the text as the mask and the gradient as the content. For a single icon or a short heading this is fine; for large amounts of text it is cheaper to pick a solid colour.

Do gradients hurt performance in React Native?

A static gradient is drawn once by the platform and costs essentially nothing, including behind a scrolling list. What costs you is animating the colors prop, since each change re-renders the native view. Animate opacity between two stacked gradients instead, or drive the animation with Reanimated on the wrapper.

→

React Native Dark Mode

Gradients need a second set of colours the moment you support both themes.

Read guide →
→

React Native Performance: 10 Fixes

Animated gradients are a re-render problem — this is the broader toolkit.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.