alouette
Version:
A modern, customizable design system built on top of NativeWind v5 with configurable defaults
300 lines (226 loc) • 9.2 kB
Markdown
---
name: alouette-setup
description: >
Wire alouette into an Expo / React Native app: withAlouetteConfig metro
plugin, import alouette/global.css with @source globs, AlouetteProvider,
SafeAreaProvider, and loading Sora / Chivo Mono font weights. Load when
bootstrapping a project, when alouette classes render unstyled, or when
fonts/bold weights look wrong. Covers ios, android and web.
type: lifecycle
library: alouette
library_version: "20.4.0"
sources:
- "christophehurpeau/alouette:packages/storybook-native-app/metro.config.cjs"
- "christophehurpeau/alouette:packages/storybook-native-app/postcss.config.mjs"
- "christophehurpeau/alouette:packages/storybook-native-app/src/global.css"
- "christophehurpeau/alouette:packages/storybook-native-app/src/App.tsx"
- "christophehurpeau/alouette:packages/alouette/src/core/AlouetteProvider.tsx"
- "christophehurpeau/alouette:packages/alouette/metro.cjs"
---
# alouette — Setup
alouette is styled with NativeWind v5 / Tailwind CSS v4. An app needs five
things wired before any component renders correctly: the metro plugin, the
PostCSS config, the CSS entry with source globs, the provider, and the fonts.
Everything targets ios/android/web from the same code.
## Setup
`metro.config.cjs`:
```js
const { withAlouetteConfig } = require("alouette/metro.cjs");
const { getDefaultConfig } = require("expo/metro-config.js");
const config = getDefaultConfig(__dirname);
module.exports = withAlouetteConfig(config);
```
`postcss.config.mjs` at the **app package root** — this is what actually runs
Tailwind. `@tailwindcss/postcss` ships as a dependency of `alouette`, so apps do
not install it; they only add this config file:
```js
export default { plugins: { "@tailwindcss/postcss": {} } };
```
Use the `.mjs` extension so it loads as ESM regardless of the package's `"type"`
field.
`src/global.css` (imported once, at the app entry). Add an `@source` for
**alouette's source** and one for **your own app source** — both are scanned
independently of the JS bundle, and anything not covered is purged:
```css
@import "alouette/global.css";
@source "./**/*.{ts,tsx}"; /* the app's own className / tv() literals */
@source "../node_modules/alouette/src/**/*.{ts,tsx,js}"; /* alouette source */
```
In a monorepo where alouette is hoisted to the **repo root** `node_modules`
(Yarn `node-modules` linker, pnpm hoisted, etc.), the path resolves from the
repo root, not the app — adjust the depth accordingly:
```css
@source "../../../node_modules/alouette/src/**/*.{ts,tsx,js}";
```
A wrong glob matches zero files and fails **silently** (no error) — utilities
are simply purged and components render unstyled.
App entry — load fonts (native), then wrap the tree in `AlouetteProvider`:
```tsx
import "./global.css";
import {
Sora_400Regular as SoraRegular,
Sora_700Bold as SoraBold,
Sora_800ExtraBold as SoraExtraBold,
useFonts,
} from "@expo-google-fonts/sora";
import { AlouetteProvider } from "alouette";
export function App() {
// Native font loading. On web, load the same fonts via a Google Fonts
// <link> instead (see "Web: load fonts from Google Fonts" below).
const [fontsLoaded] = useFonts({ SoraRegular, SoraBold, SoraExtraBold });
if (!fontsLoaded) return null;
return (
<AlouetteProvider>
<Screen />
</AlouetteProvider>
);
}
```
`AlouetteProvider` reads the OS color scheme (`useColorScheme`) and applies
`light` or `dark` as the root theme, so base tokens resolve app-wide.
Sora (body + heading) is the only required font. Add Chivo Mono **only if** the
app uses `font-mono` utilities:
```tsx
import {
ChivoMono_400Regular as ChivoMonoRegular,
ChivoMono_700Bold as ChivoMonoBold,
ChivoMono_800ExtraBold as ChivoMonoExtraBold,
} from "@expo-google-fonts/chivo-mono";
useFonts({
SoraRegular, SoraBold, SoraExtraBold,
ChivoMonoRegular, ChivoMonoBold, ChivoMonoExtraBold,
});
```
### SafeAreaProvider (only if needed)
Don't add `SafeAreaProvider` preemptively — many setups (e.g. expo-router)
already provide one. Add it only if a component throws a safe-area context error:
```tsx
import { SafeAreaProvider } from "alouette";
<SafeAreaProvider>
<AlouetteProvider>
<Screen />
</AlouetteProvider>
</SafeAreaProvider>;
```
### Web: load fonts from Google Fonts
On web, prefer a Google Fonts stylesheet over `useFonts`. With Expo Router, add
`app/+html.tsx`:
```tsx
import { ScrollViewStyleReset } from "expo-router/html";
import type { PropsWithChildren } from "react";
export default function Root({ children }: PropsWithChildren) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Sora:wght@400;700;800&display=swap"
/>
<ScrollViewStyleReset />
</head>
<body>{children}</body>
</html>
);
}
```
Append `&family=Chivo+Mono:wght@400;700;800` to the URL if you use `font-mono`.
## Common Mistakes
### CRITICAL Missing postcss.config — Tailwind never runs
Symptom: components render unstyled (only alouette's hotpink `body` fallback
shows), and the build logs spam `Warning: Unknown at rule: @utility` /
`@source` / `@theme`. Those warnings are the diagnostic signature — Tailwind
directives are reaching lightningcss un-expanded because Tailwind never ran.
Mechanism: `withAlouetteConfig` → `withNativewind` delegates CSS compilation to
Expo's Metro transform worker, which runs Tailwind **only if it finds a
`postcss.config` file at the project root**. Absent → CSS passes straight to
lightningcss verbatim and zero utilities are emitted.
Fix — add `postcss.config.mjs` at the app package root:
```js
export default { plugins: { "@tailwindcss/postcss": {} } };
```
`@tailwindcss/postcss` is a dependency of `alouette`, so no install is needed.
Source: packages/storybook-native-app/postcss.config.mjs
### CRITICAL global.css missing @source, or wrong path in a monorepo
Wrong (no `@source`, or a path that resolves to nothing):
```css
@import "alouette/global.css";
```
Correct:
```css
@import "alouette/global.css";
@source "./**/*.{ts,tsx}"; /* the app's own classes */
@source "../node_modules/alouette/src/**/*.{ts,tsx,js}"; /* alouette source */
```
Tailwind v4 only emits classes it finds in scanned files. Two failure modes,
both producing the same silent unstyled result with **no error**:
1. No `@source` for alouette's source → every alouette utility is purged.
2. No `@source` for the app's own source → the app's own classes (e.g.
arbitrary values like `from-[#f39c12]`, `bg-linear-to-t`) are purged while
alouette's still work — easy to misdiagnose.
3. In a monorepo where alouette is hoisted to the **repo root**
`node_modules`, `../node_modules/alouette/src` resolves to nothing. Use the
correct depth, e.g. `@source "../../../node_modules/alouette/src/**/*.{ts,tsx,js}"`.
Note: `@source` is a text-scan of alouette's shipped `src/*.tsx` (the verbatim
`className` / `tv()` string literals) — independent of the JS bundle, which
Metro resolves to the compiled `dist`. The two pipelines are decoupled, which is
why the scan targets `src` and not the build output.
Source: packages/storybook-native-app/src/global.css
### CRITICAL Metro config omits withAlouetteConfig
Wrong:
```js
const { getDefaultConfig } = require("expo/metro-config.js");
module.exports = getDefaultConfig(__dirname);
```
Correct:
```js
const { withAlouetteConfig } = require("alouette/metro.cjs");
const { getDefaultConfig } = require("expo/metro-config.js");
module.exports = withAlouetteConfig(getDefaultConfig(__dirname));
```
`withAlouetteConfig` enables the NativeWind / react-native-css transform; without
it, `className` styles never compile on native.
Source: packages/storybook-native-app/metro.config.cjs, packages/alouette/metro.cjs
### CRITICAL App tree not wrapped in AlouetteProvider
Wrong:
```tsx
export function App() {
return <Screen />;
}
```
Correct:
```tsx
import { AlouetteProvider } from "alouette";
export function App() {
return (
<AlouetteProvider>
<Screen />
</AlouetteProvider>
);
}
```
`AlouetteProvider` applies the OS light/dark scheme as the root `ScopedTheme`.
Without it, base tokens (`bg-surface`, `text-sharp`, `text-accent`) have no
resolved values and components render with missing colors.
Source: packages/alouette/src/core/AlouetteProvider.tsx
### HIGH Bold / extrabold fonts not loaded
Wrong:
```tsx
useFonts({ SoraRegular: Sora_400Regular });
```
Correct:
```tsx
useFonts({
SoraRegular: Sora_400Regular,
SoraBold: Sora_700Bold,
SoraExtraBold: Sora_800ExtraBold,
});
```
On native, bold and extrabold are distinct font files. If only the regular
weight is loaded, `font-body-bold` / `font-heading-extrabold` silently fall back
to regular. (Load the matching Chivo Mono weights too, but only if the app uses
`font-mono`.)
Source: packages/storybook-native-app/src/App.tsx
See also: alouette-theming/SKILL.md — once setup is done, the token/accent model
is what you style with.