https://github.com/akzhy/flairjs
A rust powered build time CSS / Style tag in JSX library
https://github.com/akzhy/flairjs
css jsx rust style
Last synced: 4 months ago
JSON representation
A rust powered build time CSS / Style tag in JSX library
- Host: GitHub
- URL: https://github.com/akzhy/flairjs
- Owner: akzhy
- Created: 2025-04-19T08:07:12.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2026-02-11T17:37:24.000Z (4 months ago)
- Last Synced: 2026-02-12T00:34:06.472Z (4 months ago)
- Topics: css, jsx, rust, style
- Language: Rust
- Homepage: https://flair.akzhy.com/
- Size: 2.46 MB
- Stars: 17
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Flair ✨
A build-time CSS-in-JSX solution that brings the power of modern CSS to your React components with zero runtime overhead.

>Note: Flair is still in early development. Expect minor bugs and issues. Feedback is welcome!
Try it online on StackBlitz.
[React Vite](https://stackblitz.com/edit/flairjs-vite-react?file=src%2FApp.tsx) | [SolidJS Vite](https://stackblitz.com/edit/solidjs-templates-famw2yzx?file=src%2FApp.tsx) | [Preact Vite](https://stackblitz.com/edit/vitejs-vite-zt9k3zr7?file=src%2Fapp.tsx)
## Features
- 🚀 **Zero Runtime** - All CSS processing happens at build time
- 💅 **Multiple Styling APIs** - Choose between `` tags, `flair()` objects, or `css` template literals
- 🌙 **Theme System** - Built-in theming with TypeScript intellisense
- 🔧 **Build Tool Integration** - Supports Vite, NextJS, Rollup, Webpack, and Parcel
- 🎯 **Scoped by Default** - Component-scoped styles with global override option
- ⚡ **Rust-Powered** - Fast AST parsing with OXC and CSS processing with Lightning CSS
- 🔍 **Static Analysis** - Class names and CSS are analyzed at build time for optimal performance
## Quick Start
### Installation
```bash
# Install client package
npm install @flairjs/client
# Install your bundler plugin
npm install @flairjs/vite-plugin # For Vite
npm install @flairjs/rollup-plugin # For Rollup
npm install @flairjs/webpack-loader # For Webpack
npm install @flairjs/parcel-transformer # For Parcel
```
### Basic Setup
#### Vite Configuration
```js
// vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import flairjs from "@flairjs/vite-plugin";
export default defineConfig({
plugins: [react(), flairjs()],
});
```
#### Component Usage
```jsx
import { flair } from "@flairjs/client";
const Button = () => {
return <button className="button">Click me!</button>;
};
// Style with flair object
Button.flair = flair({
".button": {
backgroundColor: "blue",
color: "white",
padding: "12px 24px",
borderRadius: "8px",
border: "none",
"&:hover": {
backgroundColor: "darkblue",
},
},
});
export default Button;
```
## Styling Methods
Flair provides three ways to write CSS in your components:
### 1. Flair Object API
```jsx
import { flair } from "@flairjs/client";
const Card = () => <div className="card">Content</div>;
Card.flair = flair({
".card": {
backgroundColor: "white",
borderRadius: "8px",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
padding: "16px",
},
});
```
### 2. CSS Template Literals
```jsx
import { css } from "@flairjs/client";
const Card = () => <div className="card">Content</div>;
Card.flair = css`
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
}
`;
```
### 3. Style Tag Components
```jsx
import { Style } from "@flairjs/client/react";
const Card = () => {
return (
<>
<div className="card">Content</div>
<Style>{`
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 16px;
}
`}
>
);
};
```
## Global Styles
By default, styles are scoped to components. You can make styles global:
### With Style Tag
```jsx
import { Style } from "@flairjs/client/react";
const App = () => {
return (
<>
{`
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
`}
{/* Your app content */}
>
);
};
```
### With globalFlair Property
```jsx
const App = () =>
App content;
App.globalFlair = css`
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
`;
```
## Theming
### Setup
To enable theming support, you need to:
1. **Import the theme CSS** in your top-level file (e.g., `main.tsx`, `App.tsx`, or `index.tsx`):
```jsx
import "@flairjs/client/theme.css";
```
2. **Create a theme configuration file** `flair.theme.ts` in your project root:
```typescript
// flair.theme.ts
import { defineConfig } from "@flairjs/client";
const theme = defineConfig({
prefix: "flair",
selector: "body",
tokens: {
colors: {
primary: "#3b82f6",
secondary: "#64748b",
success: "#10b981",
danger: "#ef4444",
},
fonts: {
family: "'Inter', sans-serif",
size: {
sm: "14px",
md: "16px",
lg: "18px",
},
},
space: {
1: "4px",
2: "8px",
3: "12px",
4: "16px",
5: "20px",
6: "24px",
},
},
breakpoints: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
},
});
export default theme;
export type Theme = typeof theme;
```
### Using Theme Tokens
```jsx
import { flair } from "@flairjs/client";
const Button = () => Click me;
Button.flair = flair({
".button": {
backgroundColor: "$colors.primary",
color: "white",
padding: "$space.3 $space.5",
fontSize: "$fonts.size.md",
fontFamily: "$fonts.family",
},
});
```
### TypeScript Intellisense
For theme token autocomplete, extend the `FlairTheme` interface:
```typescript
// types/flair.d.ts
import { Theme } from "../flair.theme";
declare module "@flairjs/client" {
export interface FlairTheme extends Theme {}
}
```
### Responsive Design
```jsx
Button.flair = flair({
".button": {
padding: "$space.2 $space.3",
fontSize: "$fonts.size.sm",
// Responsive breakpoints
"$screen md": {
padding: "$space.3 $space.5",
fontSize: "$fonts.size.md",
},
"$screen lg": {
padding: "$space.4 $space.6",
fontSize: "$fonts.size.lg",
},
},
});
```
## Bundler Integration
### Vite
```js
// vite.config.js
import flairjs from "@flairjs/vite-plugin";
export default {
plugins: [
flairjs({
classNameList: ["className", "class"],
// Optional: Include/exclude files
include: ["src/**/*.{tsx,jsx}"],
exclude: ["node_modules/**"],
}),
],
};
```
### Webpack
```js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(tsx|jsx)$/,
use: ["@flairjs/webpack-loader"],
},
],
},
};
```
### Rollup
```js
// rollup.config.js
import flairjs from "@flairjs/rollup-plugin";
export default {
plugins: [
// Make sure rollup is configured to handle css imports.
// Add flair before any other JSX parsers
flairjs(),
],
};
```
### Parcel
```json
// .parcelrc
{
"extends": "@parcel/config-default",
"transformers": {
"*.{tsx,jsx}": ["@flairjs/parcel-transformer", "..."]
}
}
```
## Advanced Features
### Static Analysis and Class Name Inference
Since Flair is a **build-time library**, all CSS and class names must be **statically inferrable** at build time. Flair cannot process dynamically generated CSS or class names that are only known at runtime.
#### What Works (Static Analysis)
In most cases, Flair can infer class names automatically:
```jsx
// ✅ Direct className
Click me
// ✅ Variable stored className
const buttonClass = "btn btn-primary"
Click me
// ✅ Function parameters
const variant = "primary"
Click me
```
#### What Doesn't Work (Dynamic Class Names)
```jsx
// ❌ Function calls - Flair cannot infer the return value
Click me
// ❌ Complex runtime expressions
Click me
```
### Class Name Utilities: `c()` and `cn()`
When Flair cannot directly infer a class name (e.g., when returned from a function), use the `c()` or `cn()` utilities to signal which class names should be included:
```jsx
import { c, cn } from "@flairjs/client";
// Both c() and cn() are identical - they simply return what you pass to them
// Their purpose is to signal to Flair's build-time analyzer which class names to include
function getButtonClass() {
// ✅ Signal to Flair that 'btn' and 'btn-primary' should be included
return c("btn btn-primary");
}
const Button = () => {
return Click me;
};
Button.flair = flair({
".btn": { padding: "12px 24px" },
".btn-primary": { backgroundColor: "blue" },
});
```
**Important Notes:**
- `c()` and `cn()` are **not** like `clsx` or `classnames` - they don't merge or conditionally apply classes
- They are simple pass-through functions: `c('foo')` just returns `'foo'`
- Their only purpose is to help Flair's static analyzer find class names in your code
- At runtime, they have zero overhead (they literally just return their input)
### Nesting and Pseudo-selectors
```jsx
Card.flair = flair({
".card": {
backgroundColor: "white",
"&:hover": {
backgroundColor: "#f9f9f9",
},
"&.active": {
borderColor: "$colors.primary",
},
"& .title": {
fontSize: "$fonts.size.lg",
fontWeight: "bold",
},
},
});
```
### Media Queries
```jsx
Card.flair = flair({
".card": {
padding: "$space.3",
"@media (min-width: 768px)": {
padding: "$space.5",
},
},
});
```
## Framework Support
Currently, `flair` property is supported in all JSX frameworks.
Flair component is supported in:
- ✅ React (via `@flairjs/client/react`)
- ✅ Preact (via `@flairjs/client/preact`)
- ✅ SolidJS (via `@flairjs/client/solidjs`)
## Performance
- **Zero Runtime Overhead** - All CSS is extracted at build time
- **Optimal Bundle Size** - Only the CSS you use is included
- **Fast Builds** - Rust-powered transformation with OXC and Lightning CSS
- **Efficient Caching** - Smart caching of transformed components
## Browser Support
Flair generates modern CSS that works in all evergreen browsers. Legacy browser support depends on your build setup and CSS processing pipeline.
## Contributing
We welcome contributions! Here's how to get started:
### Development Setup
1. **Clone the repository**
```bash
git clone https://github.com/akzhy/flairjs.git
cd flairjs
```
2. **Install dependencies**
```bash
pnpm install
```
3. **Build packages**
```bash
# Build all packages
pnpm build
# Or build specific packages
pnpm build:core # Build core Rust package
pnpm build:non-core-packages # Build all other packages
```
### Making Changes
When contributing changes, please follow these steps:
1. **Create a new branch** for your feature or bugfix
```bash
git checkout -b feature/your-feature-name
```
2. **Make your changes** and ensure all packages build successfully
3. **Add a changeset** to document your changes
```bash
pnpm changeset
```
This will prompt you to:
- Select which packages are affected by your changes
- Specify the type of change (major, minor, patch)
- Write a description of your changes
The changeset system ensures proper versioning and generates changelogs automatically.
4. **Commit your changes** including the changeset file
```bash
git add .
git commit -m "a sensible commit message"
```
5. **Push your branch** and create a pull request
```bash
git push origin feature/your-feature-name
```
### Changeset Guidelines
- **Patch** (0.0.x): Bug fixes, documentation updates, internal refactors
- **Minor** (0.x.0): New features, non-breaking enhancements
- **Major** (x.0.0): Breaking changes, API changes
Example changeset workflow:
```bash
# After making changes to @flairjs/vite-plugin
pnpm changeset
# You'll be prompted:
# - Select @flairjs/vite-plugin
# - Choose "patch" for a bugfix
# - Describe: "Fixed issue with theme token resolution"
```
### Testing
Before submitting a PR:
- Ensure all packages build without errors: `pnpm build`
- Test your changes in the example project: `examples/vite-react-ts`
- Run any available tests in the affected packages
### Questions?
Feel free to open an issue for any questions or discussions about contributing!
## License
MIT License - see [LICENSE](LICENSE) for details.
## Packages
This monorepo contains the following packages:
- [`@flairjs/core`](./packages/core) - Core transformation engine (Rust + NAPI)
- [`@flairjs/client`](./packages/client) - Client-side utilities and types
- [`@flairjs/bundler-shared`](./packages/shared) - Shared bundler utilities
- [`@flairjs/vite-plugin`](./packages/vite-plugin) - Vite integration
- [`@flairjs/rollup-plugin`](./packages/rollup-plugin) - Rollup integration
- [`@flairjs/webpack-loader`](./packages/webpack-loader) - Webpack integration
- [`@flairjs/parcel-transformer`](./packages/parcel-transformer) - Parcel integration