npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hueframe

v1.0.4

Published

Turn image colors into light and dark website themes. Extract palettes, adjust contrast, and export CSS variables or JSON design tokens. Zero runtime dependencies.

Readme

hueframe

Turn a photo into a website colour scheme.

hueframe extracts a palette, builds light and dark themes, and exports CSS variables or JSON design tokens. You get colours for the page background, surfaces, text, primary actions, and accents, plus label colours for buttons.

Use it for a theme picker, a site builder, or a page that takes its colours from a cover image. It works in browsers and Node.js, has no runtime dependencies, and includes TypeScript types.

Created by Niko Minadze at IndexLane.

Live playground · GitHub · Report an issue · IndexLane

Install

npm install hueframe

The package ships compiled JavaScript and type declarations. Use ESM import; no build step is needed to use the installed package. For Node.js, use version 20.9 or later.

Make your first theme

Start with a few colours so you can try the full flow without loading an image:

import { extractPalette, createThemeSet, exportThemeSetCss } from 'hueframe';

const palette = extractPalette([
  '#234c45',
  '#80a18a',
  '#d2a467',
  '#f3eee4',
  '#292d32',
]);

const themes = createThemeSet(palette);
const css = exportThemeSetCss(themes);

console.log(css);

The result contains a :root block for the light theme and a [data-theme="dark"] block for the dark theme. Save it in your stylesheet, then use the variables:

body {
  background: var(--hf-background);
  color: var(--hf-text);
}

.card {
  background: var(--hf-surface);
}

.button {
  background: var(--hf-primary);
  color: var(--hf-on-primary);
}

Set data-theme="dark" on your <html> element to switch modes. To generate an additional prefers-color-scheme: dark media query, pass { prefersColorScheme: true } to exportThemeSetCss. That media query follows the system setting even if you set a light theme manually, so omit it when your application manages the mode itself.

Use an image

extractPalette accepts either a list of hex colours or an object containing decoded RGBA pixels: { data, width, height }. Browser ImageData already has that shape.

In a browser

This helper reads a local File, scales it to a maximum of 360 pixels on its longest edge, and returns its dominant colours. Nothing is uploaded.

import { extractPalette } from 'hueframe';

async function paletteFromFile(file) {
  const image = await createImageBitmap(file);

  try {
    const scale = Math.min(1, 360 / Math.max(image.width, image.height));
    const canvas = document.createElement('canvas');
    canvas.width = Math.max(1, Math.round(image.width * scale));
    canvas.height = Math.max(1, Math.round(image.height * scale));

    const context = canvas.getContext('2d');
    if (!context) throw new Error('A 2D canvas context is unavailable.');

    context.drawImage(image, 0, 0, canvas.width, canvas.height);
    const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
    return extractPalette(pixels, { count: 5 });
  } finally {
    image.close();
  }
}

Call await paletteFromFile(file) from your file input, drop handler, or clipboard handler, then pass the palette to createThemeSet. If the image has no eligible pixels, such as a fully transparent image, extraction returns []; check for that before generating a theme.

In Node.js

Pass decoded RGBA bytes as data, with the image dimensions. A Buffer or Uint8ClampedArray works. This small example represents a two-pixel image:

import { extractPalette } from 'hueframe';

const palette = extractPalette({
  data: new Uint8ClampedArray([
    35, 76, 69, 255,
    210, 164, 103, 255,
  ]),
  width: 2,
  height: 1,
});

console.log(palette); // Each entry has hex, rgb, and population.

hueframe does not download images or decode JPEG, PNG, or WebP files. Decode the file with your image library first. An encoded file buffer, URL, or file path cannot be passed directly.

Extraction options

extractPalette(imageData, { count: 5, seed: 1 });

| Option | Default | What it controls | | --- | --- | --- | | count | 6 | Maximum number of colours returned. | | seed | 1 | Seed used when clustering image pixels. | | maxSamples | 24000 | Sampling budget for large images. | | ignoreAlphaBelow | 125 | Skip pixels whose alpha is below this value. | | mergeThreshold | 0.045 | Merge image clusters this close in Oklab. | | minPopulation | 0.008 | Filter small image clusters, keeping the largest if all are below the threshold. |

Results are sorted by population: the share of retained samples represented by each colour. Image weights are rounded to four decimal places, so their total can differ slightly from 1. The same input and options produce the same result. For hex lists, repeated colours determine population; image sampling and clustering options do not apply.

Adjust the theme

Each theme contains five roles:

| Role | Use it for | | --- | --- | | background | The page background. | | surface | Cards, panels, and section backgrounds. | | text | Body text and headings. | | primary | Main action fills. | | accent | Secondary highlights. |

Read a role from theme.roles.primary.hex, or call themeColors(theme) to get a flat map. For text on coloured buttons, use theme.readableInk.onPrimary and theme.readableInk.onAccent.

You can edit a colour, lock it, and generate another variation around it:

import {
  extractPalette,
  generateTheme,
  setRoleColor,
  setRoleLocked,
  regenerateTheme,
  themeColors,
} from 'hueframe';

const palette = extractPalette(['#234c45', '#80a18a', '#d2a467', '#f3eee4']);
let theme = generateTheme(palette, { mode: 'light' });

theme = setRoleColor(theme, 'primary', '#315b50');
theme = setRoleLocked(theme, 'primary', true);
theme = regenerateTheme(theme);

console.log(themeColors(theme)); // The primary colour stays #315b50.

These functions return new theme objects. Manual edits are replaced on regeneration unless the role is locked. Changing a primary or accent fill recalculates its label colour.

generateTheme accepts mode, seed, variation, locked, overrides, and readability. createThemeSet accepts the same options except mode, because it creates both modes. Use overrides: { primary: '#315b50' } to set a colour during generation.

Export CSS or JSON

| Function | Returns | | --- | --- | | themeToCssVariables(theme) | A map of custom property names to colours. | | exportCssVariables(theme) | One theme as a CSS string. | | exportThemeSetCss(themes) | Light and dark CSS blocks. | | exportJsonTokens(theme) | One theme as a JSON-compatible object. | | exportJsonTokensString(theme) | One theme as a JSON string. | | exportThemeSetJson(themes) | An object with light and dark token documents. |

CSS exports include --hf-background, --hf-surface, --hf-text, --hf-primary, --hf-accent, --hf-on-primary, and --hf-on-accent. Pass { includePalette: true } to add --hf-sample-1 and the remaining sampled colours.

You can also set prefix, selector, darkSelector, includeColorScheme, and indent. CSS strings include color-scheme by default; the flat map contains only the colour variables.

JSON colour tokens use $type, $value, and $description fields, with hex strings as values. Palette weights and contrast results are included by default. Use { includePalette: false, includeContrast: false } to omit them. exportJsonTokensString also accepts pretty and indent.

Check contrast

Theme generation adjusts lightness and chroma toward these default contrast targets:

  • text: 7 for body text against the background and surface.
  • ink: 4.5 for labels on primary and accent fills.
  • ui: 3 for primary and accent fills against the background.

Pass readability: { text: 7, ink: 4.5, ui: 3 } to change them. Use checkThemeContrast(theme) for the six foreground/background pairs and their ratios, or isThemeReadable(theme, readability) to check whether all pairs meet your targets.

An override or manual edit can introduce poor contrast, and a requested target may be out of reach. Check the final theme, particularly after edits. These colour checks do not assess the accessibility of a complete page.

The package also exports contrastRatio, pickReadableInk, enforceContrast, RGB/HSL/Oklab conversions, and colour mixing utilities. See the export list for the full API; type declarations are included in the package.

Try the playground

Try the live playground with sample photos, editable roles, and two page templates.

The GitHub repository includes the playground source. To run it locally:

git clone https://github.com/lame13/hueframe.git
cd hueframe
npm ci
npm run dev

Open http://localhost:3000. The playground is separate from the npm package; installing hueframe does not install Next.js or React.

License

MIT.