# react-native-image-colors: pulling prominent colors out of an image

> This Expo module gives React Native applications one API for reading the dominant colors of an image, backed by AndroidX Palette on Android, UIImageColors on iOS and `node-vibrant` on the web. The convenience is real, and so is the catch: the three platforms return differently named and differently shaped results.

**osamaqarem/react-native-image-colors** — Fetch prominent colors from an image.

- Repository: https://github.com/osamaqarem/react-native-image-colors
- Stars: 817 · Forks: 47
- Language: Swift
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/osamaqarem-react-native-image-colors

## One API over three different colour extractors

React-native-image-colors fetches prominent colors from an image, and it does so by wrapping three separate implementations: the Palette class from AndroidX on Android, the UIImageColors library on iOS and `node-vibrant` for the web. The package is built as an Expo module, with android and ios directories holding the native sides and src holding the JavaScript interface, and it declares support for Android, iOS, Expo and web.

The design is straightforward and the trade-off is legible. You call one function with an image reference and get back a promise, and the module handles the differences in how each platform library wants to be given an image. The Palette class on Android expects a bitmap, UIImageColors expects an image object and `node-vibrant` works on data in JavaScript, so a single call has to cover downloading a URL, decoding a base64 payload, resolving a local require and reading a content URI, then handing each to the right place.

The consequence of having three engines is that the results are not identical. The README notes this directly, warning that because the implementation of each platform is different you can get different colors from the same picture depending on where the app runs. Anyone building a design system that derives its accents from user images has to decide what to do about that before shipping.

## Installing for Expo, for the React Native CLI and for the web

The package installs the same way in all three cases, and the differences are in what has to be true of your project first.

For an Expo project the requirement is Expo SDK 47 or newer. You add the dependency, then regenerate the native projects, because the module has native code that a managed workflow cannot run without:

```bash
yarn add react-native-image-colors
```

```bash
npx expo prebuild
```

After that the app runs on a device or simulator through the usual Expo run commands, with npx expo run:ios for iOS and npx expo run:android for Android. The repository ships an example application built as an Expo app, referenced from the README as the place to look for a working setup.

For a bare React Native project the requirements are React Native 0.70.0 or newer, iOS 13.4 or newer, and that Expo modules must be configured in the project. That last requirement is the one that surprises people: even outside Expo, this package is distributed as an Expo module, so a project that has never set up Expo modules needs that step before the native code links. Users on React Native older than 0.69 are pointed to the 1.x.x versions of the package instead.

On iOS you then install the pods and run the app:

```bash
npx pod-install
```

```bash
npx react-native run-ios
```

For Android the equivalent command is npx react-native run-android. The web target needs nothing beyond the install, since the web implementation is `node-vibrant` running in JavaScript.

## Calling getColors from a React component

The usage example in the README wraps the call in a small hook, which is the shape most applications end up with because the result arrives asynchronously:

```js
import React from 'react'
import { getColors } from 'react-native-image-colors'

const useImageColors = () => {
  const [colors, setColors] = React.useState(null)

  React.useEffect(() => {
    const url = 'https://i.imgur.com/68jyjZT.jpg'

    getColors(url, {
      fallback: '#228B22',
      cache: true,
      key: url,
    }).then(setColors)
  }, [])

  return colors
}
```

Three things are worth noticing in that snippet. The component starts with a null value and fills it in when the promise resolves, so anything rendering the colours has to handle the empty state. The promise is not wrapped in a catch handler, which means a failed download leaves the state null and, depending on your runtime, produces an unhandled rejection. And the fallback colour is set explicitly here, which is how you avoid the default black when extraction fails.

The API signature is getColors with a URI string and an optional config object, returning a promise of a result that always carries a platform key so you can narrow the type in TypeScript.

## The four kinds of image reference it accepts

The uri parameter is a string, and the README documents four forms it can take.

A remote URL is the plainest case, for example an https address. A local file is referenced through a require call, which is how a bundled image is addressed in a React Native project:

```js
const catImg = require('./images/cat.jpg')
```

A base64 data string works as well, and the README is explicit that the mime type prefix is required rather than optional:

```js
const catImgBase64 = 'data:image/jpeg;base64,/9j/4Ri...'
```

That prefix matters because it is how the native side knows how to decode the payload, and omitting it produces a failure rather than a fallback.

The fourth form is Android specific, a content URI pointing at a media provider entry:

```js
const contentUri = 'content://media/external/images/media/123'
```

A content URI is the shape you get from the system image picker on Android, so an app that lets a user pick a photo will meet it, while iOS has no equivalent in this list. The type system of the library reflects that asymmetry, and any code that builds the uri string has to handle the platform difference.

## Config options and the three different result shapes

The config object is optional, and every option is documented with its default and the platforms it applies to.

The fallback property is the colour used when a colour property could not be retrieved, defaulting to the hex string #000000, and it applies on all platforms. The cache property enables in-memory caching of the result so the same image is not downloaded twice, and it is off by default. The key property is the unique identifier for that cache entry, defaulting to the image URI, and the README advises passing it explicitly if you enable caching while using a base64 string as the URI, since a long data string makes a poor key. The headers property sends HTTP headers with the download request and applies on iOS and Android.

Two options are platform specific. pixelSpacing is how many pixels to skip when iterating over the image, higher meaning better performance, with a default of 5 and a value that cannot go below 1, and it applies on Android only. The quality option, with values from lowest through highest, where highest implies no downscaling and very good colors, defaults to low and applies on iOS and the web.

The results then diverge in shape. Android returns dominant, average, `vibrant`, darkVibrant, lightVibrant, darkMuted, lightMuted and muted. The web returns the same list minus average. iOS returns a different set entirely: background, primary, secondary and detail. So the property names are not portable across platforms, only the platform field tells you which vocabulary you are holding.

## Where react-native-image-colors is the wrong tool

The first limitation is the one the README raises in its own notes: the colours differ by platform. If your product needs a consistent accent colour taken from a photo, and that colour is then shown to users on iOS and Android, the wrapper does not give you a single answer. The alternative is to pick one extraction algorithm, run it everywhere, and accept that you are not using the platform's own palette library on each OS.

The second limitation is the surface area exposed. The wrapper passes through a handful of options, but the libraries underneath offer more: Palette and `node-vibrant` both work in terms of swatches and quantisation settings, and none of that appears in the config table. If your design depends on a specific palette algorithm or on a fixed number of swatches, this is the wrong level of abstraction.

The third limitation is caching. The cache is in-memory only, so it disappears when the app restarts, and it does nothing about the repeated work that happens when the same image is analysed again after a navigation. For a list of thumbnails there is no cache at all, and each cell that calls getColors pays the download and decode cost.

The fourth is the web implementation. `node-vibrant` runs in JavaScript, which means the web path is a different algorithm from the native ones rather than the same one, and the result set reflects that. A project that treats web as a first-class target has to accept a different set of colours from the same image.

## Packaging, release tooling and licence

The package is published on npm as react-native-image-colors, with the version currently at 2.6.1 and the most recent release tagged on 2026-10-19, after 2.6.0 in March 2026 and 2.5.1 in November 2025. The repository's last push was on the same day as the release, so the project is actively maintained rather than abandoned.

Its build and quality scripts all delegate to the Expo module toolchain: expo-module build, clean, lint, test, prepare and prepublishOnly. The published entry point is build/index.js with type declarations at build/index.d.ts, and the source is TypeScript with a tsconfig.json, an eslint configuration and a prettier configuration alongside it. An example app lives in its own directory with its own package.json and yarn.lock, and the repository carries a yarn.lock at the root.

Runtime dependencies are deliberately thin: `node-vibrant` at version 4 or newer, which is what the web path uses. Everything else is a development dependency, including expo-modules-core, react-native 0.72.1 for the test environment, jest with the jest-expo preset, eslint, prettier and release-it with the conventional-changelog plugin. The peer dependencies are wide open, listing expo, react and react-native as any version, which keeps installation frictionless at the cost of leaving compatibility to you, so check the stated requirements, React Native 0.70 or newer and iOS 13.4 or newer, against your own project.

The licence is MIT, with a LICENSE file at the root of the repository. That is a permissive licence with no copyleft obligations on applications that depend on it.

## Conclusion

Adopt react-native-image-colors if your app needs a palette from an image on more than one platform and you would rather not maintain three separate colour extraction paths. Do not adopt it if you need one consistent colour vocabulary across platforms, because the result objects differ in both names and contents, or if you need the advanced options of the underlying libraries, which the wrapper does not expose. Verify first that your Expo SDK is 47 or newer or your React Native version is 0.70 or newer with Expo modules configured, that the image you pass is a form the module accepts on that platform, and that your UI code handles the platform field in the result rather than assuming a single shape.

## FAQ

### How do I install react-native-image-colors?

Run yarn add react-native-image-colors. Expo projects need SDK 47 or newer plus npx expo prebuild, while React Native CLI projects need 0.70.0 or newer, iOS 13.4 or newer and Expo modules configured, followed by npx pod-install. Users below React Native 0.69 should use the 1.x.x versions.

### What does getColors return on each platform?

Android returns dominant, average, `vibrant`, darkVibrant, lightVibrant, darkMuted, lightMuted and muted. The web returns the same set without average. iOS returns a different vocabulary: background, primary, secondary and detail. Every result carries a platform key.

### Can react-native-image-colors read local or base64 images?

Yes. A local file can be passed with require, a base64 payload works as a data string but must include the mime type prefix, and Android content URIs are supported as well.

### What does the cache and key option do?

The cache option enables in-memory caching of the result so the same image is not downloaded again, and it is off by default. The key option sets the cache entry identifier, which defaults to the image URI, and the README advises setting it explicitly when the URI is a base64 string.

## Sources

- [Issues](https://github.com/osamaqarem/react-native-image-colors/issues)
- [License: MIT](https://github.com/osamaqarem/react-native-image-colors/blob/master/LICENSE)
- [osamaqarem/react-native-image-colors on GitHub](https://github.com/osamaqarem/react-native-image-colors)
- [README](https://github.com/osamaqarem/react-native-image-colors/blob/master/README.md)
- [Releases](https://github.com/osamaqarem/react-native-image-colors/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/osamaqarem-react-native-image-colors
