@expo-forge/forge-ui
v0.0.50
Published
ForgeUI — utility-first CSS/React Native style engine
Maintainers
Readme
ForgeUI
ForgeUI is a utility-first styling engine for:
- Web apps (Forge scans classes and generates CSS)
- React Native / Expo apps (Forge uses a Babel plugin, no CSS file)
v0.0.10 — Babel plugin now automatically injects
useTheme()from @expo-forge/forge-ui into all components withdark:classes, ensuring they automatically re-render across all pages when theme changes (no manual hook call needed).
v0.0.9 — Theme preference is now persisted across app restarts using AsyncStorage (React Native) and localStorage (web). The theme will remember your toggle choice.
v0.0.6 — Native theme detection now uses useColorScheme as the primary source, which fixes Expo system theme detection.
Quick Start
- Install:
npm install @expo-forge/forge-ui- Choose setup path:
- Web: see Web Setup
- React Native / Expo: see React Native / Expo Setup
Web Setup
Step 1: Create forge.config.toml
Create this file in your project root:
[general]
input = [
"src/**/*.html",
"src/**/*.jsx",
"src/**/*.tsx",
"src/**/*.vue",
"src/**/*.svelte",
]
# CSS only (JS goes next to it):
output = "dist/forge.css"
# — or — separate dirs for CSS and JS:
# output = "public/css, public/js"
[options]
important = false
minify = false
[components]
enabled = true
js = trueWhen
components.enabled = true, Forge only emits CSS for components whose class names appear in your scanned source files. Unused component blocks (button, card, modal, etc.) produce no output.
Step 2: Build or watch
One-time build:
npx forge buildDev watch mode:
npx forge devStep 3: Load generated files
Required CSS:
<link rel="stylesheet" href="/forge.css" />Optional runtime (needed for interactive Forge components and dev auto-refresh):
<script src="/forge-runtime.js" defer></script>Web Setup By Platform
Plain HTML
- Include
src/**/*.htmlingeneral.input. - Run
npx forge buildornpx forge dev. - Load generated files:
<link rel="stylesheet" href="/dist/forge.css" />
<script src="/dist/forge-runtime.js" defer></script>React / Vite
- Include
src/**/*.jsxand/orsrc/**/*.tsxingeneral.input. - Run
npx forge dev. - Import generated CSS in app entry file:
import "../dist/forge.css";If using interactive Forge components, include forge-runtime.js in index.html.
Next.js
Use this config:
[general]
input = ["app/**/*.tsx", "app/**/*.ts", "components/**/*.tsx"]
output = "public/forge.css"
[options]
important = false
minify = false
[components]
enabled = true
js = trueLoad in root layout:
<head>
<link rel="stylesheet" href="/forge.css" />
</head>
<body>
{children}
<script src="/forge-runtime.js" defer></script>
</body>Vue
- Include
src/**/*.vueingeneral.input. - Run
npx forge dev. - Import generated CSS in app entry file:
import "../dist/forge.css";Svelte
- Include
src/**/*.svelteingeneral.input. - Run
npx forge dev. - Import generated CSS in root entry/layout file:
import "../dist/forge.css";React Native / Expo Setup
Important
React Native does not use:
- forge.config.toml
- forge.css
- forge-runtime.js
Step 1: Initialize
npx forge init --nativeStep 2: Enable Babel plugin
Use:
@expo-forge/forge-ui/babel-pluginStep 3: Wrap app with ThemeProvider
import { ThemeProvider } from "@expo-forge/forge-ui";
export default function RootLayout() {
return <ThemeProvider>{/* app */}</ThemeProvider>;
}Step 4: Start Expo
npx expo startRequired app.json settings
{
"expo": {
"userInterfaceStyle": "automatic",
"experiments": {
"reactCompiler": false
}
}
}Step 5: Use Forge color palettes in native
You can now use all Forge color palettes in your native components:
import { useColors } from "@expo-forge/forge-ui";
export default function MyComponent() {
const colors = useColors();
return <View style={{ backgroundColor: colors.blue[5] }} />;
}Dynamic Classes (Web)
Literal class strings are supported:
const card = {
color: "bg-gradient-to-r from-blue-1 to-green-1",
};For fully runtime-generated class names (example: bg-${tone}-5), use:
general.safelistgeneral.inject_colors = true(larger CSS output)
Output Optimizations
Forge automatically tree-shakes generated CSS — no extra config needed.
Selective :root color variables
The :root block only contains variables for color palettes you actually use. A project that only uses bg-blue-5 text-red-3 gets --blue-* and --red-* variables only — not all 22 palettes.
Lazy component CSS
When components.enabled = true, Forge only emits CSS for components whose class names appear in scanned files:
| Component block | Emitted when you use… |
| --------------- | --------------------------------------------------------------------------------------------------------- |
| Button | button, button-primary, button-secondary, button-ghost, button-danger, button-sm, button-lg |
| Card | card, card-header, card-body, card-footer |
| Modal | modal-content, data-fg-modal, data-fg-toggle |
| Utility | avatar, dot, spark, bar, table-wrap |
| Visibility | hidden |
Advanced Utilities
Fluid text sizing
Use text-clamp-* for responsive font sizes with CSS clamp(...):
<h1 class="text-clamp-4xl">Fluid heading</h1>
<p class="text-clamp-sm">Fluid body text</p>Supported presets: sx/xs, sm, base, lg, xl, 2xl ... 9xl.
Class-driven at-rules
Forge can emit custom at-rules directly from class tokens:
<div class="media-[(min-width:900px)]:text-clamp-xl">Custom media query</div>
<div class="layer-[components]:rounded-lg">Wrapped in @layer components</div>
<div
class="keyframes-[wiggle:0%{transform:rotate(-3deg)}100%{transform:rotate(3deg)}] animate-[wiggle_1.2s_ease-in-out_infinite]"
>
Custom keyframes + animation
</div>Notes:
- Replace spaces with underscores inside bracket syntax when needed.
- These tokens work with existing variants like
md:,hover:, anddark:.
Config Reference
| Key | Type | Default | Purpose |
| ------------------------------- | ---------- | --------------------- | ------------------------------------------------ |
| general.input | string[] | common src/** globs | Files to scan for classes |
| general.output | string | forge.css | CSS output (and optional JS output) — see below |
| general.safelist | string[] | [] | Always include these classes |
| general.inject_colors | bool | false | Pre-generate full color and gradient utility set |
| general.inject_color_variants | string[] | [] | Variant prefixes for injected classes |
| options.important | bool | false | Append !important to declarations |
| options.minify | bool | false | Minify output CSS |
| components.enabled | bool | true | Include built-in component CSS |
| components.js | bool | true | Emit forge-runtime.js |
output format
# CSS only — JS written next to it
output = "dist/forge.css"
# CSS in one dir, JS in another
output = "public/css, public/js"
# Bare directories — filenames appended automatically
# public/css/forge.css + public/js/forge-runtime.js
output = "public/css, public/js"
# Explicit filenames also work
output = "public/css/styles.css, public/js/runtime.js"Common Mistakes
No styles applied
- Run
npx forge build - Confirm file exists at
general.output - Confirm app loads the same file path
Some classes missing
- Your file globs likely miss some folders
- Add those folders to
general.input
Browser does not update during dev
- Use
npx forge dev - Ensure
/forge-runtime.jsis loaded
React Native styles do not apply
- Run
npx forge init --native - Ensure Babel plugin is configured
- Restart Metro
Command Summary
Same commands on Windows, macOS, and Linux:
npx forge build
npx forge dev
npx forge init --nativeRepository
https://github.com/semsakadanupol/forge-ui
Web-like first: and last: pseudo-classes in React Native
With Forge-UI's experimental Babel plugin support, you can use first: and last: pseudo-classes directly in className inside a .map loop—just like on the web!
Example:
{
items.map((item) => (
<View key={item.id} className="p-4 first:bg-red-1 last:rounded-b-xl">
{item.text}
</View>
));
}first:bg-red-1will only apply to the first child.last:rounded-b-xlwill only apply to the last child.- No manual index checks or helpers needed—just write your classes as you would for web.
This works for any dynamic array mapping in JSX in React Native.
