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

alouette

v24.2.0

Published

A modern, customizable design system built on top of NativeWind v5 with configurable defaults

Readme

Introduction

Alouette provides a comprehensive set of universal components that render on both web and React Native, styled entirely through Tailwind className via NativeWind v5. Themes, accents, and design tokens ship as CSS custom properties that cascade through the tree, so components stay declarative and consistent across platforms.

🚀 Getting Started

Prerequisites

  • Node.js >= 22.18.0 (includes Corepack for package management)
  • A React Native / Expo app using Metro, or a web app using NativeWind v5

Installation

npm install alouette
# or with yarn
yarn add alouette

alouette-icons is installed automatically as a dependency.

Install the peer dependencies if your app does not already provide them:

npm install [email protected] tailwindcss@^4 \
  react-native-reanimated react-native-svg

expo-web-browser, react-dom, and react-native-reanimated are optional peers — add them only if your target needs them (react-dom for web, react-native-reanimated for native animations, expo-web-browser for ExternalLink).

Configuration

Alouette relies on NativeWind v5's Metro + PostCSS pipeline. NativeWind discovers themes and utilities from the imported global.css and scans your sources via @source directives — there is no JS config to maintain.

  1. Metro — wrap your config with the Alouette helper:
// metro.config.cjs
const { withAlouetteConfig } = require("alouette/metro.cjs");
const { getDefaultConfig } = require("expo/metro-config.js");

module.exports = withAlouetteConfig(getDefaultConfig(__dirname));

withAlouetteConfig currently wraps withNativewind and forwards your options unchanged; use it anyway, so alouette can add required metro wiring later without a breaking change on your side.

  1. CSS entry — create a global.css that re-exports Alouette's tokens and points @source at the directories NativeWind should scan for class names:
/* global.css */
@import "alouette/global.css";

@source './src'; /* your own className / tv() literals */
@source '../node_modules/alouette/src'; /* alouette's source — required */

Tailwind only emits classes it finds while scanning @source paths, so both your app's source and alouette's source must be covered or the matching utilities are silently purged. In a monorepo where alouette is hoisted to the repo root node_modules, adjust the depth (e.g. @source '../../../node_modules/alouette/src'); a path that resolves to nothing fails silently with no error.

alouette/global.css is a convenience aggregator of alouette/core.css (structural, color-free) + alouette/default-palette.css (the default palette in sRGB hex). Wide-gamut color is opt-in — add alouette/default-palette-oklch.css after it to give web the display-p3 ramp (see Color format):

@import "alouette/global.css";
@import "alouette/default-palette-oklch.css"; /* optional */

To ship your own palette instead of the default, import core.css + your own generated palette CSS — see Custom palette. The @source lines stay the same in every case.

Import it once at your app's entry point:

import "./global.css";
  1. PostCSS — NativeWind compiles Tailwind through PostCSS on both web and native. @tailwindcss/postcss ships as a dependency of alouette, so you only add the config file (use .mjs so it loads as ESM regardless of your package's "type"):
// postcss.config.mjs
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};
  1. Babel — use the Expo preset and the Reanimated/worklets plugin:
// babel.config.js
export default function (api) {
  api.cache(true);
  return {
    presets: [["babel-preset-expo", { reanimated: false }]],
    plugins: ["react-native-worklets/plugin"],
  };
}
  1. Provider — wrap your app in AlouetteProvider. It applies the OS light/dark scheme as the root theme so base tokens resolve app-wide:
import { AlouetteProvider } from "alouette";

export function App() {
  return <AlouetteProvider>{/* your app */}</AlouetteProvider>;
}
  1. Fonts — Alouette's typography uses Sora (body/heading) and Chivo Mono (mono). On native, load the weight-specific font files (the standalone font-weight utility has no effect because each weight is a distinct file):
import {
  Sora_400Regular as SoraRegular,
  Sora_700Bold as SoraBold,
  Sora_800ExtraBold as SoraExtraBold,
  useFonts,
} from "@expo-google-fonts/sora";
import {
  ChivoMono_400Regular as ChivoMonoRegular,
  ChivoMono_700Bold as ChivoMonoBold,
  ChivoMono_800ExtraBold as ChivoMonoExtraBold,
} from "@expo-google-fonts/chivo-mono";

const [fontsLoaded] = useFonts({
  SoraRegular,
  SoraBold,
  SoraExtraBold,
  ChivoMonoRegular,
  ChivoMonoBold,
  ChivoMonoExtraBold,
});

🎨 Core Features

Components

Alouette ships a universal component set styled through className:

  • Actions — Button, ExternalLinkButton, InternalLinkButton, IconButton
  • Containers — Box (raised with the surface utility), InteractiveBox, SafeAreaBox, Popover, ScopedTheme, AccentScope, PresenceOne, PresenceList
  • Inputs — InputText, InputCode, InputTextAutocomplete, TextArea, Switch
  • Feedback — Message, InfoMessage, ConfirmationMessage, WarningMessage
  • Data — PressableBox, PressableListItem
  • Layout — GradientBackground, GradientScrollView
  • Primitives — View, Text, Paragraph, Icon, ScrollView, Separator
  • Responsive — SwitchBreakpointsUsingDisplayNone, SwitchBreakpointsUsingNull, useCurrentBreakpointName

For detailed examples and API documentation, visit our Storybook.

Autocomplete

InputTextAutocomplete is a text input backed by a filtered listbox, built on downshift:

import { InputTextAutocomplete } from "alouette";

<InputTextAutocomplete
  aria-label="Fruit"
  placeholder="Search a fruit..."
  options={[
    { label: "Apple", value: "apple" },
    { label: "Banana", value: "banana" },
    { label: "Durian (sold out)", value: "durian", disabled: true },
  ]}
  onValueChange={setFruit}
/>;

Options are filtered by a case-insensitive match on their label; pass filterOption for anything else. Selection is controllable through value/defaultValue/onValueChange, and the text through inputValue/defaultInputValue/onInputValueChange.

Text styling

<Text> has no variant props — style it entirely via className. Family and weight are combined into a single utility (font-body, font-body-bold, font-heading-extrabold, font-mono, …); size uses standard Tailwind text-*; color uses tokens like text-sharp, text-muted, text-accent.

import { Text } from "alouette";

<Text className="text-base">Body</Text>;
<Text className="font-heading-extrabold text-4xl">Title</Text>;
<Text className="font-mono text-xs text-muted">Code</Text>;

Theming and accents

Themes are sets of CSS variables (light, dark, light_brand, dark_info, …) applied by ScopedTheme. Child components use base tokens (bg-surface, text-accent, border-muted, …) and inherit the correct values from the nearest theme scope. Most components take an accent prop that re-themes their subtree; AccentScope is for grouping several children that don't take one:

import { AccentScope, Box, Text } from "alouette";

<Box accent="info" className="surface">
  {/* children use base tokens */}
</Box>;

<AccentScope accent="info">
  <Text className="text-accent">Title</Text>
  <Text className="text-accent">Subtitle</Text>
</AccentScope>;

The two platforms resolve a theme differently, and only one of them uses CSS: web renders the theme name as a className the palette CSS resolves, while native pushes the theme's variables through NativeWind's VariableContextProvider, from the themeVariables map you give AlouetteProvider. The .<theme> blocks are therefore emitted inside @supports (display: contents), which the native compiler drops — native compiles the @theme defaults and nothing else, and gets every themed value from the map.

That also means colors are the one part of alouette you cannot read from JS: there is no exported token hook, so style every color with a token className.

Custom palette (bring your own)

An app can generate its own coherent palette for the existing accents (brand, danger, info, success, warning, plus grayscale) while staying on alouette's OKLCH ramp — and ship only that palette, no default CSS. A theme has two coupled outputs: the palette CSS (what web resolves, and the source of every className token) and the runtime themeVariables map (what native resolves through VariableContextProvider). alouette/theme-generator produces both from per-accent hue params — the same module alouette's own scripts/build-css.ts uses for the default palette.

The app generates its palette the same way: a build script calls writeTheme, which writes both files to disk. Override only the accents you want to re-color (the rest inherit alouette's defaults; omit overrides entirely to reproduce the default palette):

// scripts/build-theme.ts
import { writeTheme } from "alouette/theme-generator";

writeTheme({
  outDir: "src",
  overrides: { brand: { type: "accent", hue: 300 } },
});
// package.json
"scripts": {
  "build:theme": "node --experimental-strip-types scripts/build-theme.ts"
}

That writes three files, all marked DO NOT EDIT and already formatter-stable: src/palette.css + src/themeVariables.ts in sRGB hex (complete on their own), and src/palette-oklch.css, the OKLCH / display-p3 overlay. Re-run it whenever the palette params change, and commit the output. Pass srgbOnly: true to skip the OKLCH file entirely. generateTheme returns the same content in memory ({ css, oklchCss, themeVariables, oklchThemeVariables }) if the app would rather write the files itself.

The OKLCH file is how the palette gets wide-gamut colors on web while staying renderable on native (see Color format). Opting in is a CSS import: add ./palette-oklch.css after ./palette.css. There is no OKLCH counterpart to themeVariables.ts — the web build of AlouetteProvider ignores themeVariables entirely, so the hex map native consumes is the only one.

Import alouette/core.css + your generated palette (instead of alouette/global.css), and pass the generated map to AlouetteProvider so native token reads match your palette CSS:

/* global.css */
@import "alouette/core.css";
@import "./palette.css";

@source './src';
@source '../node_modules/alouette/src';
import { AlouetteProvider } from "alouette";
import { themeVariables } from "./themeVariables";

export function App() {
  return (
    <AlouetteProvider themeVariables={themeVariables}>
      {/* your app */}
    </AlouetteProvider>
  );
}

PaletteSpec params per accent: type ("accent" | "brightAccent" | "grayscale"), hue (0–360), optional hueHi / hueLo (hue ramp across the lightness range) and intensity (chroma multiplier). The accent set is fixed — generateTheme re-colors the existing accents, it does not add new ones.

Color format: hex everywhere, OKLCH as a web opt-in

Every palette is computed in OKLCH and emitted twice:

  • sRGB hex — the baseline, on every platform. React Native's color parser accepts hex / rgb / hsl / hwb only, so oklch() must never reach it.
  • OKLCH with display-p3 chroma headroom — web only. Same lightness and hue ramp, more chroma, so accents are visibly more vivid on wide-gamut screens.

A theme has two halves — the palette CSS and the themeVariables map — but only the CSS half has an OKLCH variant in play. Opting in is one extra import, not a platform check:

| | palette CSS | themeVariables map | | -------------- | ----------------------------------------- | -------------------------------------------- | | hex (default) | alouette/default-palette.css | alouette/defaultThemeVariables (= …Srgb) | | + OKLCH on web | also alouette/default-palette-oklch.css | unchanged — the overlay is CSS-only |

The OKLCH CSS re-declares the same variables inside @supports (color: oklch(0 0 0)), so it is additive: browsers apply the override, and the native compiler drops the feature query and keeps the hex even if the file ends up in a shared CSS entry. The map stays on hex in every case — it is read only on native, where oklch() cannot be parsed at all.

Because the map is unambiguously hex, code that parses a token value (a test reading hex channels, say) can read alouette/defaultThemeVariables directly.

Icons

Icons come from the integrated alouette-icons package:

import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeft";

function MyComponent() {
  return <ArrowLeftRegularIcon />;
}

🎯 Examples

Basic Button

import { Button } from "alouette";

function MyComponent() {
  return (
    <Button
      accent="brand"
      text="Click me"
      onPress={() => console.log("Clicked!")}
    />
  );
}

Button with Icon

import { Button } from "alouette";
import { ArrowLeftRegularIcon } from "alouette-icons/phosphor-icons/ArrowLeft";

function MyComponent() {
  return (
    <Button accent="brand" icon={<ArrowLeftRegularIcon />} text="Go Back" />
  );
}

🤖 Using an AI agent?

Alouette ships skills that teach AI coding agents how to use the design system correctly:

npx @tanstack/intent@latest install

🏗️ Architecture

  • Universal Design — components render across web and native from one API
  • NativeWind v5 styling — Tailwind className; animations are CSS @keyframes + --animate-* tokens, run on native via Reanimated
  • Token-based theming — CSS custom properties cascade through ScopedTheme; light/dark + accent scopes
  • Accessibility — proper ARIA / accessibility attributes
  • Type Safety — built with TypeScript

📚 Documentation

📄 License

ISC © Christophe Hurpeau