# ⚒️ forge ``` ╔═╝╔═║╔═║╔═╝╔═╝ ╔═╝║ ║╔╔╝║ ║╔═╝ ╝ ══╝╝ ╝══╝══╝ ``` ## overview Forge is a typed, local, variant-driven way to organize CSS and create self-contained TSX components out of discrete parts. ## css problems - Styles are global and open - anything can override anything anywhere. - No IDE-friendly link between the class name in markup and its definition. - All techniques are patterns a human must know and follow, not APIs. - Errors happen silently. ## forge solutions - All styles are local to your TSX components. - Styles defined using TS typing. - Component styles are made up of independently styled "Parts". - "Variants" replace inline styles with typed, declarative parameters. - Style composition is deterministic. - Themes are easy. - Errors and feedback are provided. ## examples ### styles ```tsx import { define } from "forge" export const Button = define("button", { base: "button", padding: 20, background: "blue", }) // Usage ``` ### variants ```tsx import { define } from "forge" export const Button = define("button", { base: "button", padding: 20, background: "blue", variants: { status: { danger: { background: "red" }, warning: { background: "yellow" }, } }, }) // Usage ``` ### parts + `render()` ```typescript export const Profile = define("div", { padding: 50, background: "red", parts: { Header: { display: "flex" }, Avatar: { base: "img", width: 50 }, Bio: { color: "gray" }, }, variants: { size: { small: { parts: { Avatar: { width: 20 } }, }, }, }, render({ props, parts: { Root, Header, Avatar, Bio } }) { return (
{props.bio}
) }, }) // Usage: import { Profile } from "./whatever" console.log() console.log() ``` ### selectors Use `selectors` to write custom CSS selectors. Reference the current element with `&` and other parts with `@PartName`: ```tsx const Checkbox = define("Checkbox", { parts: { Input: { base: "input[type=checkbox]", display: "none", }, Label: { base: "label", padding: 10, cursor: "pointer", color: "gray", selectors: { // style Label when Input is checked "@Input:checked + &": { color: "green", fontWeight: "bold", }, // style Label when Input is disabled "@Input:disabled + &": { opacity: 0.5, cursor: "not-allowed", }, }, }, }, render({ props, parts: { Root, Input, Label } }) { return ( ) }, }) // Usage ``` ## themes built-in support for CSS variables with full type safety: ```tsx // themes.tsx - Define your themes import { createThemes } from "forge" export const theme = createThemes({ dark: { bgColor: "#0a0a0a", fgColor: "#00ff00", sm: 12, lg: 24, }, light: { bgColor: "#f5f5f0", fgColor: "#0a0a0a", sm: 12, lg: 24, }, }) // Use theme() in your components import { define } from "forge" import { theme } from "./themes" const Button = define("Button", { padding: theme("spacing-sm"), background: theme("colors-bg"), color: theme("colors-fg"), }) ``` Theme switching is done via the `data-theme` attribute: ```tsx // Toggle between themes document.body.setAttribute("data-theme", "dark") document.body.setAttribute("data-theme", "light") ``` The `theme()` function is fully typed based on your theme keys, giving you autocomplete and type checking throughout your codebase. ## scopes Sometimes you want your parts named things like ButtonRow, ButtonCell, ButtonTable, etc, but all those Button's are repetitive: ```typescript const { define } = createScope("Button") // css class becomes "Button" const Button = define("Root", { // becomes "Button" // ... }) // css class becomes "ButtonRow" const ButtonRow = define("Row", { // ... }) // css class becomes "ButtonContainer" const ButtonContainer = define("Container", { // ... }) ``` ## see it Check out the `examples/` dir and view them at http://localhost:3300 by cloning this repo and running the local web server: ``` bun install bun dev open http://localhost:3300 ```