# llms.txt and llms-full.txt files Source: https://better-styled.com/ai/llms-txt Machine-readable documentation index and full content files for AI assistants. ## llms.txt A lightweight index of all documentation pages. AI tools use this to find relevant information quickly. ``` https://better-styled.com/llms.txt ``` ### What's Inside * Page titles and URLs * Brief descriptions from each page * Organized by documentation sections ### Format ```markdown theme={"theme":{"light":"min-light","dark":"synthwave-84"}} # Better Styled ## Docs - [Introduction](https://better-styled.com/introduction): Overview of better-styled - [Installation](https://better-styled.com/installation): How to install - [styled()](https://better-styled.com/api/styled): API reference ... ``` This file is automatically generated and always up-to-date with the documentation. *** ## llms-full.txt The entire documentation combined into a single file. Use this when you need complete context. ``` https://better-styled.com/llms-full.txt ``` ### Best For * Complex implementation questions * Understanding the full API * Building complete components from scratch * Training custom AI models on better-styled ### Usage with ChatGPT Paste the URL when starting a conversation: ``` Use this documentation for better-styled: https://better-styled.com/llms-full.txt Help me create a Button component with size and variant props. ``` This file is larger and uses more tokens. Use `llms.txt` for simple queries. *** ## Comparison | Feature | llms.txt | llms-full.txt | | ----------- | ---------------------- | ------------------ | | Size | \~2KB | \~50KB+ | | Content | Index only | Full documentation | | Use case | Finding specific pages | Complete context | | Token usage | Low | High | Learn about the capability summary file # MCP Server for real-time documentation access Source: https://better-styled.com/ai/mcp-server Connect better-styled documentation directly to AI tools via Model Context Protocol. ## What is MCP? [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) is an open standard that lets AI tools access external data sources in real-time. With MCP, your AI assistant can search and fetch the latest better-styled documentation directly, without you needing to copy/paste. ## MCP Server URL ``` https://better-styled.com/mcp ``` ## Claude Code Add better-styled docs to Claude Code with a single command: ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} claude mcp add --transport http better-styled https://better-styled.com/mcp ``` Verify the connection: ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} claude mcp list ``` That's it! Claude Code now has access to the full documentation. ## Claude Web Connect within Claude's web application: Navigate to [Connectors](https://claude.ai/settings/connectors) in Claude settings. Click "Add custom connector". * **Name**: `better-styled` * **URL**: `https://better-styled.com/mcp` Click the attachments button and select `better-styled` to include the documentation in your conversation. ## Claude Desktop Add to your `claude_desktop_config.json`: File location: `~/Library/Application Support/Claude/claude_desktop_config.json` ```json theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { "mcpServers": { "better-styled": { "type": "http", "url": "https://better-styled.com/mcp" } } } ``` File location: `%APPDATA%\Claude\claude_desktop_config.json` ```json theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { "mcpServers": { "better-styled": { "type": "http", "url": "https://better-styled.com/mcp" } } } ``` Restart Claude Desktop after making changes. ## Cursor Add to your `mcp.json` file (access via `Cmd+Shift+P` → "Open MCP settings"): ```json theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { "mcpServers": { "better-styled": { "url": "https://better-styled.com/mcp" } } } ``` ## VS Code Create `.vscode/mcp.json` in your project: ```json theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { "servers": { "better-styled": { "type": "http", "url": "https://better-styled.com/mcp" } } } ``` ## Verifying the Connection After setup, ask your AI assistant: ``` What is better-styled and how do I create a Button component? ``` If configured correctly, it will search the documentation and provide accurate answers. ## How It Works The MCP server allows AI tools to: 1. **Search** the documentation for relevant information 2. **Fetch** specific pages when needed 3. **Stay current** with real-time access to the latest docs MCP servers don't consume context until actively searched. You can connect multiple MCP servers without impacting performance. ## Troubleshooting 1. Verify the URL is correct: `https://better-styled.com/mcp` 2. Check your internet connection 3. Try removing and re-adding: ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} claude mcp remove better-styled claude mcp add --transport http better-styled https://better-styled.com/mcp ``` 1. Make sure you added it in [Connectors settings](https://claude.ai/settings/connectors) 2. Refresh the page 3. Check that the URL includes `/mcp` at the end MCP fetches documentation in real-time, so it should always be current. If you see outdated info, try restarting your AI tool. See all AI integration options # Connect better-styled docs to AI assistants Source: https://better-styled.com/ai/overview This documentation is optimized for AI. Connect it to Claude, ChatGPT, Cursor, and other tools for accurate code generation. This documentation is optimized for AI assistants. Here's how to connect it to your favorite tools. ## Available Endpoints | Endpoint | Purpose | Best For | | --------------------------------------------- | ---------------------- | ------------------------------------------------- | | [`/llms.txt`](/ai/llms-txt) | Documentation index | Quick reference lookup | | [`/llms-full.txt`](/ai/llms-txt#llms-fulltxt) | Complete documentation | Full context for complex tasks | | [`/skill.md`](/ai/skill-md) | Capability summary | AI agents understanding what better-styled can do | | [MCP Server](/ai/mcp-server) | Real-time access | Direct integration with AI tools | ## Quick Start ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} claude mcp add --transport http better-styled https://better-styled.com/mcp ``` Go to [Connectors](https://claude.ai/settings/connectors) and add: ``` https://better-styled.com/mcp ``` Add to `mcp.json` (`Cmd+Shift+P` → "Open MCP settings"): ```json theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { "mcpServers": { "better-styled": { "url": "https://better-styled.com/mcp" } } } ``` Paste this URL when starting a conversation: ``` https://better-styled.com/llms-full.txt ``` ## What Each File Does Lightweight index of all pages. AI tools use this to find relevant information quickly. Complete documentation in one file. Use for complex tasks that need full context. Structured capability summary. Tells agents what better-styled can do and how to use it. Real-time access via Model Context Protocol. Direct integration with AI tools. ## Example Prompts Once connected, try these prompts: ``` Using better-styled, create a Card component with: - Variants: size (sm, md, lg) and variant (elevated, outlined) - Slots: Header, Body, Footer - Context propagation so children inherit the variant ``` ``` Convert this CVA component to better-styled: [paste your CVA code] ``` ``` Create a React Native Button using better-styled with NativeWind. Include size variants and a Text slot. ``` Get started with MCP integration # skill.md capability file Source: https://better-styled.com/ai/skill-md A structured file that tells AI agents what better-styled can do and how to use it correctly. ## What is skill.md? A machine-readable capability file following the [agentskills.io](https://agentskills.io) specification. It tells AI agents: * **What** better-styled can do * **When** to use it * **How** to generate correct code ``` https://better-styled.com/skill.md ``` ## Why skill.md? | File | Purpose | | -------- | ---------------------------------------------- | | llms.txt | Directory - where to find information | | skill.md | Capability summary - what actions are possible | AI agents process skill.md to understand your product's capabilities before taking actions. ## What's Inside The skill.md includes: ```yaml theme={"theme":{"light":"min-light","dark":"synthwave-84"}} name: better-styled description: Create type-safe styled components with variants... license: MIT compatibility: React, React Native, TypeScript, Tailwind CSS ``` * Variant-based components * Context propagation (parent → child variants) * Compound components with slots * React Native with NativeWind/Uniwind Complete code examples for: * `styled()` - creating components * `createStyledContext()` - variant propagation * `withSlots()` - compound components * Utility functions Rules for generating correct code: * Use arrow functions (not React.FC) * Let TypeScript infer types * Context patterns for design systems ## Using skill.md ### With AI Assistants Reference it in your prompts: ``` Use the skill.md at https://better-styled.com/skill.md to create a Button component with size variants. ``` ### With Cursor / Copilot Add to `.cursorrules` or `.github/copilot-instructions.md`: ```markdown theme={"theme":{"light":"min-light","dark":"synthwave-84"}} When working with better-styled: 1. Import from "better-styled" 2. Use styled() for creating components 3. Use createStyledContext() for variant propagation 4. Use withSlots() for compound components 5. Never use React.FC - use arrow functions 6. Types are inferred automatically - don't add manual types Reference: https://better-styled.com/skill.md ``` ### With Context7 better-styled is indexed by [Context7](https://context7.com), allowing AI tools to fetch up-to-date documentation automatically. ``` @context7 how do I create a Button with better-styled? ``` ## Tips for AI Assistants When components have children that should inherit variants (like Button with Icon), use `createStyledContext()`. Instead of exporting `CardRoot`, `CardHeader`, `CardBody` separately, use `withSlots()` to create `Card.Header`, `Card.Body`. TypeScript infers variant types automatically. Don't add manual type annotations unless needed. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Correct const Button = ({ children }: ButtonProps) => { ... } // Incorrect const Button: React.FC = ({ children }) => { ... } ``` Real-time documentation access # API reference for creating styled contexts Source: https://better-styled.com/api/create-styled-context Creates a context for sharing variants between parent and child components. ## Signature ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} function createStyledContext( variants: T ): StyledContext ``` ## Parameters An object defining the variant names and their possible values. Each key is a variant name. Each value is an array of possible values. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} { size: ["sm", "md", "lg"], variant: ["primary", "secondary"], disabled: ["boolean"], } ``` ### Special Values Use `["boolean"]` for true/false variants. This produces a proper `boolean` type instead of `"true" | "false"`. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} createStyledContext({ disabled: ["boolean"], // Type: boolean loading: ["boolean"], // Type: boolean }); ``` ## Returns An object with: The underlying React context. Rarely needed directly. The context provider component. Used internally by styled components. Hook to read current variant values. Returns `null` outside a provider. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const context = createStyledContext({ size: ["sm", "lg"] }); function Child() { const variants = context.useVariants(); // variants: { size: "sm" | "lg" } | null } ``` The original variants object. Used for type inference. ## Examples ### Basic ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext } from "better-styled"; const CardContext = createStyledContext({ size: ["sm", "md", "lg"], elevated: ["boolean"], }); ``` ### With styled() ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const CardContext = createStyledContext({ variant: ["default", "outlined"], }); const CardRoot = styled("div", { context: CardContext, variants: { variant: { default: { className: "bg-white shadow" }, outlined: { className: "border-2 border-gray-200" }, }, }, }); const CardTitle = styled("h2", { context: CardContext, base: { className: "font-bold" }, variants: { variant: { default: { className: "text-gray-900" }, outlined: { className: "text-gray-700" }, }, }, }); ``` ### Multiple Variants ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const ButtonContext = createStyledContext({ size: ["xs", "sm", "md", "lg", "xl"], variant: ["solid", "outline", "ghost", "link"], colorScheme: ["blue", "red", "green", "gray"], disabled: ["boolean"], loading: ["boolean"], }); ``` ### Using useVariants ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const TabsContext = createStyledContext({ size: ["sm", "lg"], }); function TabIndicator() { const variants = TabsContext.useVariants(); if (!variants) return null; return (
); } ``` ## Type Inference Types are automatically inferred from the arrays: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const ctx = createStyledContext({ size: ["sm", "md", "lg"], // "sm" | "md" | "lg" disabled: ["boolean"], // boolean variant: ["a", "b", "c", "d"], // "a" | "b" | "c" | "d" }); // Inferred type: // { // size: "sm" | "md" | "lg" // disabled: boolean // variant: "a" | "b" | "c" | "d" // } ``` No `as const` needed. ## Notes Context values are provided when a parent component with `context` receives variant props **or** has `defaultVariants` defined. Children automatically inherit these values. Only variants defined here will propagate to children. You can define additional **local variants** in `styled()` that stay on a single component. See [Local Variants](/concepts/context#local-variants). Always define arrays inline in `createStyledContext()`. Assigning them to variables first can break type inference. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Correct createStyledContext({ size: ["sm", "lg"] }); // ❌ Wrong - loses literal types const sizes = ["sm", "lg"]; createStyledContext({ size: sizes }); ``` ## See Also * [Context Concept Guide](/concepts/context) * [styled()](/api/styled) # API reference for the styled function Source: https://better-styled.com/api/styled Creates a styled component with variant support. The core function of better-styled. ## Signature ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // With context function styled(component: T, config: { context: StyledContext, base?: Props, variants?: { [name: string]: { [value: string]: Props } }, defaultVariants?: { [name: string]: string | boolean }, compoundVariants?: CompoundVariant[], }): StyledComponent // Without context function styled(component: T, config: { base?: Props, variants?: { [name: string]: { [value: string]: Props } }, defaultVariants?: { [name: string]: string | boolean }, compoundVariants?: CompoundVariant[], }): StyledComponent ``` ## Parameters The base component to style. Can be: * HTML element string: `"button"`, `"div"`, `"span"` * React component: `Link`, `Pressable` * Another styled component Configuration object with the following properties: ### Config Properties Props that always apply, regardless of variants. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} base: { className: "rounded-lg", role: "button", } ``` Variant definitions. Each key is a variant name, each value is an object mapping option names to props. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} variants: { size: { sm: { className: "h-8" }, md: { className: "h-10" }, lg: { className: "h-12" }, }, color: { blue: { className: "bg-blue-500" }, red: { className: "bg-red-500" }, }, } ``` When using `context`, variants defined here but **not** in `createStyledContext()` become **local variants**. They work on this component but don't propagate to children. See [Local Variants](/concepts/context#local-variants). Default values for variants when not specified. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} defaultVariants: { size: "md", color: "blue", } ``` Array of compound variant definitions. Each applies when all conditions match. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} compoundVariants: [ { size: "lg", color: "blue", props: { className: "shadow-lg" }, }, ] ``` Context for variant propagation between parent and child components. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const ButtonContext = createStyledContext({ ... }); styled("button", { context: ButtonContext, // ... }); ``` ## Returns A React component with: * All props from the base component * Variant props based on your config * Automatic type inference ## Examples ### Basic ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; const Button = styled("button", { base: { className: "px-4 py-2 rounded font-medium" }, variants: { variant: { primary: { className: "bg-blue-600 text-white" }, secondary: { className: "bg-gray-200 text-gray-900" }, }, }, defaultVariants: { variant: "primary", }, }); ``` ### With Context ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const ButtonContext = createStyledContext({ size: ["sm", "md", "lg"], }); const Button = styled("button", { context: ButtonContext, variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, }); ``` ### With React Native ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { Pressable } from "react-native"; const Button = styled(Pressable, { base: { className: "rounded-lg px-4 py-2" }, variants: { isDisabled: { true: { className: "opacity-50", disabled: true }, }, }, }); ``` ⚠️ Use `isDisabled` instead of `disabled` to avoid shadowing `Pressable`'s native prop. ### Boolean Variants ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Input = styled("input", { variants: { hasError: { true: { className: "border-red-500" }, }, }, }); ``` You only need to define `true`. The `false` case uses base styles by default. ### Compound Variants ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Badge = styled("span", { variants: { size: { sm: {}, lg: {} }, color: { blue: {}, red: {} }, }, compoundVariants: [ { size: "lg", color: "blue", props: { className: "ring-2 ring-blue-300" }, }, { size: "lg", color: "red", props: { className: "ring-2 ring-red-300" }, }, ], }); ``` ## Props Merging Behavior | Prop Type | Merge Behavior | | --------------------------- | ------------------------------------- | | `className` | 🔀 Merged with `tailwind-merge` | | `style` | 🔀 Object merged with `Object.assign` | | Functions (`onClick`, etc.) | ⛓️ Composed (all execute) | | Other props | ⬆️ Later values override | Priority order (lowest to highest): 1. 🥉 `base` 2. 🥈 `variants` 3. 🥈 `compoundVariants` 4. 🥇 Direct props ## See Also * [styledConfig()](/api/styled-config) — Share a config between multiple components * [styled() Concept Guide](/concepts/styled) * [Variants](/concepts/variants) * [Context](/concepts/context) # API reference for the styledConfig function Source: https://better-styled.com/api/styled-config Creates a typed config object shared between multiple styled components. Same type inference as styled() — zero generics needed. ## Signature ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // With context function styledConfig( components: [T, U], config: { context: StyledContext, base?: Props & Props, variants?: { [name: string]: { [value: string]: Props & Props } }, defaultVariants?: { [name: string]: string | boolean }, compoundVariants?: CompoundVariant[], } ): typeof config // Without context function styledConfig( components: [T, U], config: { base?: Props & Props, variants?: { [name: string]: { [value: string]: Props & Props } }, defaultVariants?: { [name: string]: string | boolean }, compoundVariants?: CompoundVariant[], } ): typeof config ``` ## Parameters A tuple of two components that will share this config. Used only for type inference — not stored or used at runtime. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} styledConfig([UniwindImage, UniwindImageBg], { ... }) ``` Configuration object. Identical structure to `styled()` config — see [styled() Config Properties](/api/styled#config-properties) for details. The config is validated against **both** components. If a variant value sets a prop that doesn't exist on one of the components, TypeScript will error. ## Returns The exact same config object (identity function). Zero runtime overhead — exists purely for type inference. ## Example ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const config = styledConfig([UniwindImage, UniwindImageBg], { context: ImageCtx, base: { className: "rounded-lg" }, variants: { variant: { solid: { className: "bg-black" }, bordered: { className: "border-2" }, }, }, }); const StyledImage = styled(UniwindImage, config); const StyledImageBg = styled(UniwindImageBg, config); ``` ## Type Safety The config is validated against both components simultaneously. If a variant sets a prop that only exists on one component, TypeScript will catch it: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ❌ TypeScript error — "onLoad" exists on Image but not on Text const config = styledConfig([Image, Text], { variants: { variant: { solid: { onLoad: () => {} }, }, }, }); ``` ## See Also * [Shared Config](/concepts/context#shared-config) — When and why to use styledConfig * [styled()](/api/styled) — Create individual styled components * [TypeScript Guide](/guides/typescript#shared-configs) — Type inference details # API reference for cn() Source: https://better-styled.com/api/utilities Merges class names with Tailwind conflict resolution. Combines clsx and tailwind-merge. ## Signature ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} function cn(...inputs: ClassValue[]): string | undefined ``` ## Parameters Any number of class values: strings, arrays, objects, undefined, null, or false. ## Returns A merged class string, or `undefined` if the result is empty. ## Example ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { cn } from "better-styled"; cn("px-4 py-2", "px-8"); // → "py-2 px-8" (px-8 overrides px-4) cn("text-red-500", condition && "text-blue-500"); // → "text-blue-500" if condition is true cn("base", undefined, null, false, "active"); // → "base active" cn(); // → undefined (not empty string) ``` ## How It Works `cn` combines [clsx](https://github.com/lukeed/clsx) for conditional classes with [tailwind-merge](https://github.com/dcastil/tailwind-merge) for conflict resolution. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // clsx handles conditionals cn("base", isActive && "active", { "error": hasError }); // tailwind-merge handles conflicts cn("p-4", "p-8"); // → "p-8" not "p-4 p-8" cn("text-sm", "text-lg"); // → "text-lg" ``` ## When to Use Most of the time, you won't need `cn()` directly — `styled()` handles class merging for you. Use it when you need to merge classes outside of a styled component: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} function CustomButton({ className, ...props }) { return ( // better-styled: automatic inheritance 🎉 ``` Children know their parent's variants automatically. Change the parent, children adapt. ### Function Composition Event handlers from base, variants, and direct props all execute - nothing gets overwritten. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled(Pressable, { variants: { haptics: { medium: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium), }, }, }, }); // Both handlers run: haptics first, then your handler ``` This is perfect for design system features like haptics, sound effects, or analytics. ### React Native: Components vs Class Strings All variant libraries work with React Native when combined with NativeWind or Uniwind. The difference is in **what they output**: | Library | Output | React Native | | ----------------- | ---------------- | ---------------------------------------- | | **better-styled** | React components | ✅ Works directly with any component | | Tailwind Variants | Class strings | ⚠️ Need to manually apply to `className` | | CVA | Class strings | ⚠️ Need to manually apply to `className` | | Windstitch | Class strings | ⚠️ Need to manually apply to `className` | | classnames | Class strings | ⚠️ Need to manually apply to `className` | ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Other libraries: manual className assignment const buttonClass = tv({ base: "px-4 py-2", variants: { ... } }); // better-styled: it's already a component const Button = styled(Pressable, { base: { className: "px-4 py-2" }, variants: { ... } }); ``` ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // After: better-styled import { styled } from "better-styled"; const Button = styled("button", { base: { className: "px-4 py-2 rounded" }, variants: { color: { primary: { className: "bg-blue-600 text-white" }, secondary: { className: "bg-gray-200 text-gray-800" }, }, }, }); // Usage ``` ### From CVA ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Before: CVA import { cva } from "class-variance-authority"; const button = cva("px-4 py-2 rounded", { variants: { intent: { primary: "bg-blue-600 text-white", secondary: "bg-gray-200 text-gray-800", }, }, defaultVariants: { intent: "primary", }, }); // Usage ``` ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // After: better-styled import { styled } from "better-styled"; const Button = styled("button", { base: { className: "px-4 py-2 rounded" }, variants: { intent: { primary: { className: "bg-blue-600 text-white" }, secondary: { className: "bg-gray-200 text-gray-800" }, }, }, defaultVariants: { intent: "primary", }, }); // Usage ``` ### From classnames ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Before: classnames import cx from "classnames"; function Button({ size, variant, className, ...props }) { return ( ``` ## Summary | Library | Best For | | --------------------- | -------------------------------------------------------------------------------------- | | **better-styled** | Design systems with parent-child relationships, React Native apps, compound components | | **Tailwind Variants** | Class generation with slots, projects that need responsive variants | | **CVA** | Minimal bundle size, simple class generation | | **Windstitch** | Stitches-like API, lightweight styling | | **classnames** | Simple conditional class joining without variants | Ready to try better-styled? # Share variants between parent and child components Source: https://better-styled.com/concepts/context Context is what makes better-styled special. It lets parent components share their variants with children automatically. ## The Problem Imagine a Button with an Icon inside. You want the icon size to match the button size. Without context, you'd do this: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Repetitive and error-prone ``` If you change the button size, you have to remember to change the icon size too. ## The Solution With context, the icon knows its parent's size automatically: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Icon inherits size from Button ``` ## Creating a Context Use `createStyledContext()` to define which variants should be shared. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext } from "better-styled"; const ButtonContext = createStyledContext({ size: ["sm", "md", "lg"], variant: ["primary", "secondary", "ghost"], }); ``` The arrays define the possible values. TypeScript will infer the union types automatically. ## Connecting Components Pass the context to each component that should participate. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled, withSlots } from "better-styled"; // Parent component const ButtonRoot = styled("button", { context: ButtonContext, base: { className: "inline-flex items-center gap-2" }, variants: { size: { sm: { className: "h-8 px-3 text-sm" }, md: { className: "h-10 px-4 text-base" }, lg: { className: "h-12 px-6 text-lg" }, }, variant: { primary: { className: "bg-blue-600 text-white" }, secondary: { className: "bg-gray-200 text-gray-900" }, ghost: { className: "hover:bg-gray-100" }, }, }, }); // Child component - inherits from context const ButtonIcon = styled("span", { context: ButtonContext, base: { className: "shrink-0" }, variants: { size: { sm: { className: "w-4 h-4" }, md: { className: "w-5 h-5" }, lg: { className: "w-6 h-6" }, }, }, }); // Another child const ButtonLabel = styled("span", { context: ButtonContext, variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, }); // Combine into compound component export const Button = withSlots(ButtonRoot, { Icon: ButtonIcon, Label: ButtonLabel, }); ``` ## How It Works When you pass variant props to the parent: 1. The parent renders with those variants 2. It wraps its children in a context provider 3. Children with the same context read the values 4. Children apply matching variants automatically ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ButtonIcon gets size="lg" from context // ButtonLabel gets size="lg" from context ``` ## Default Variants Propagation When the parent component has `defaultVariants`, children automatically inherit them—even without explicit props. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { Pressable, Text } from "react-native"; const ButtonRoot = styled(Pressable, { context: ButtonContext, base: { className: "flex-row items-center justify-center rounded-lg" }, variants: { size: { sm: { className: "h-8 px-3" }, md: { className: "h-10 px-4" }, lg: { className: "h-12 px-6" }, }, variant: { primary: { className: "bg-blue-600" }, secondary: { className: "bg-gray-200" }, }, }, defaultVariants: { size: "md", variant: "primary", // These propagate to children }, }); const ButtonLabel = styled(Text, { context: ButtonContext, base: { className: "font-semibold" }, variants: { variant: { primary: { className: "text-white" }, secondary: { className: "text-gray-900" }, }, }, }); ``` Now you can use the component without any props: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // No props needed - defaultVariants are shared // Button gets variant="primary" and size="md" // Button.Label gets variant="primary" from parent's defaultVariants // Result: white text on blue background ``` This is powerful for design systems where you want sensible defaults that cascade through the component tree. ## Overriding Context Children can override the context values when needed. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` Direct props always win over context values. ## Boolean Variants For true/false variants, use the special `["boolean"]` marker. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const ButtonContext = createStyledContext({ size: ["sm", "md", "lg"], isDisabled: ["boolean"], // Becomes TypeScript boolean }); const ButtonRoot = styled("button", { context: ButtonContext, variants: { isDisabled: { true: { className: "opacity-50", disabled: true }, }, }, }); ``` Just define `true`. You don't need `false: {}`. When using the component, pass actual booleans: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` ## Local Variants Not all variants need to propagate to children. Some behaviors are specific to a single component—like haptic feedback on a Button root, but not on its Text or Icon slots. ### The Problem Imagine you want your Button to have haptic feedback options, but this only makes sense for the Pressable root: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ❌ This doesn't make sense ``` ### The Solution Define variants **outside** of `createStyledContext()` to keep them local: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { Pressable, Text } from "react-native"; import * as Haptics from "expo-haptics"; // Only "variant" propagates to children const ButtonContext = createStyledContext({ variant: ["solid", "bordered", "ghost"], }); // Haptics is NOT in the context - it's local only const haptics = { soft: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Soft) }, light: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) }, heavy: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy) }, }; const ButtonRoot = styled(Pressable, { context: ButtonContext, base: { className: "flex-row items-center justify-center rounded-lg" }, variants: { variant: { solid: { className: "bg-blue-600" }, bordered: { className: "border-2 border-blue-600" }, ghost: { className: "bg-transparent" }, }, haptics, // ← Local variant, not in context }, defaultVariants: { variant: "solid", haptics: "heavy", // ← Has a default, but won't propagate }, }); const ButtonText = styled(Text, { context: ButtonContext, base: { className: "font-semibold" }, variants: { variant: { // ← Only "variant" is available here solid: { className: "text-white" }, bordered: { className: "text-blue-600" }, ghost: { className: "text-blue-600" }, }, // No haptics here - ButtonText doesn't know about it }, }); ``` ### How It Works 1. **Context variants** (`variant`) - Defined in `createStyledContext()`, propagate to all children with the same context 2. **Local variants** (`haptics`) - Defined only in `variants`, stay on that component ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ButtonRoot: variant="solid", haptics="light" ✓ // ButtonText: variant="solid" (inherited), haptics=undefined (local to root) ``` ### When to Use Local Variants ✅ **Use local variants for:** * Platform-specific behaviors (haptics, animations) * Root-only interactions (press effects, gestures) * Variants that don't make semantic sense on children ❌ **Use context variants for:** * Visual consistency (size, color, variant) * Semantic states (disabled, loading) * Anything children should know about ### Function Composition Local variants work great with function props. When you define `onPress` in a variant, it composes with any `onPress` passed to the component: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Both haptics AND your custom handler execute // → Haptics fire, then "clicked" logs ``` ## Shared Config When multiple components share the same context and variants, you end up duplicating the config: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ❌ Duplicated config const StyledImage = styled(UniwindImage, { context: ImageCtx, variants: { variant: { solid: { className: "bg-black" }, bordered: { className: "border-2 border-gray-300" }, }, }, }); const StyledImageBg = styled(UniwindImageBg, { context: ImageCtx, variants: { variant: { solid: { className: "bg-black" }, bordered: { className: "border-2 border-gray-300" }, }, }, }); ``` Use `styledConfig()` to create a single config validated against both components: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext, styled, styledConfig, withSlots } from "better-styled"; import { Image as ExpoImage, ImageBackground } from "expo-image"; import { withUniwind } from "uniwind"; const UniwindImage = withUniwind(ExpoImage); const UniwindImageBg = withUniwind(ImageBackground); const ImageCtx = createStyledContext({ variant: ["solid", "bordered", "light"], }); // ✅ Single source of truth const config = styledConfig([UniwindImage, UniwindImageBg], { context: ImageCtx, base: { className: "rounded-lg" }, variants: { variant: { solid: { className: "bg-black" }, bordered: { className: "border-2 border-gray-300" }, light: { className: "bg-gray-100" }, }, }, defaultVariants: { variant: "solid", }, }); const ImageBase = styled(UniwindImage, config); const ImageBackgroundBase = styled(UniwindImageBg, config); export const Image = withSlots(ImageBase, { Background: ImageBackgroundBase, }); ``` `styledConfig()` is an identity function — it returns the config unchanged with zero runtime overhead. It exists purely so TypeScript can validate the config against both components and give you full autocomplete. `styledConfig()` uses the same type inference as `styled()`. No generics needed — just pass the components array and the config. Signature, parameters, and type safety details ## Nested Contexts If you nest components with the same context, the nearest parent wins. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` ## Without Context Not every component needs context. For standalone components, just omit it: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // No context - variants are local only const Badge = styled("span", { variants: { color: { gray: { className: "bg-gray-100" }, red: { className: "bg-red-100" }, }, }, }); ``` Use context when you have parent-child relationships. Skip it for standalone elements. Build compound components with dot notation # Build compound components with the slots pattern Source: https://better-styled.com/concepts/slots Slots let you build compound components with intuitive dot notation, like or . ## The Pattern Instead of importing multiple components: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { CardRoot, CardHeader, CardBody, CardFooter } from "./Card"; Title Content Actions ``` You import one component with slots: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { Card } from "./Card"; Title Content Actions ``` Cleaner imports. Clear relationships. Better discoverability. ## Creating Slots Use `withSlots()` to attach child components to a parent. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled, withSlots } from "better-styled"; // Create individual components const CardRoot = styled("div", { base: { className: "rounded-xl border bg-white shadow-sm" }, }); const CardHeader = styled("div", { base: { className: "px-6 py-4 border-b font-semibold" }, }); const CardBody = styled("div", { base: { className: "px-6 py-4" }, }); const CardFooter = styled("div", { base: { className: "px-6 py-4 border-t bg-gray-50" }, }); // Combine them export const Card = withSlots(CardRoot, { Header: CardHeader, Body: CardBody, Footer: CardFooter, }); ``` Now `Card.Header`, `Card.Body`, and `Card.Footer` are available. ## Slots + Context Slots become powerful when combined with context. Children automatically inherit parent variants. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext, styled, withSlots } from "better-styled"; const AlertContext = createStyledContext({ intent: ["info", "success", "warning", "error"], }); const AlertRoot = styled("div", { context: AlertContext, base: { className: "rounded-lg p-4 flex gap-3" }, variants: { intent: { info: { className: "bg-blue-50 text-blue-900" }, success: { className: "bg-green-50 text-green-900" }, warning: { className: "bg-yellow-50 text-yellow-900" }, error: { className: "bg-red-50 text-red-900" }, }, }, }); const AlertIcon = styled("div", { context: AlertContext, base: { className: "shrink-0 w-5 h-5" }, variants: { intent: { info: { className: "text-blue-500" }, success: { className: "text-green-500" }, warning: { className: "text-yellow-500" }, error: { className: "text-red-500" }, }, }, }); const AlertTitle = styled("h3", { context: AlertContext, base: { className: "font-medium" }, }); const AlertDescription = styled("p", { context: AlertContext, base: { className: "text-sm opacity-90" }, }); export const Alert = withSlots(AlertRoot, { Icon: AlertIcon, Title: AlertTitle, Description: AlertDescription, }); ``` Usage: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}}
Payment successful Your order has been confirmed.
``` All slots automatically get `intent="success"` styling. When slot components share the same variants and context, use [`styledConfig()`](/concepts/context#shared-config) to avoid duplicating the config. ## Nested Slots You can add slots to components that already have slots. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const BaseCard = withSlots(CardRoot, { Header: CardHeader, Body: CardBody, }); // Add more slots const Card = withSlots(BaseCard, { Footer: CardFooter, Actions: CardActions, }); // Has all four: Header, Body, Footer, Actions ``` ## displayName Slots preserve the original component's display name for React DevTools. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = withSlots(ButtonRoot, { Icon: ButtonIcon, Label: ButtonLabel, }); // In React DevTools: // // // // ``` ## When to Use Slots ✅ **Use slots when:** * Components are meant to be used together * You want dot notation for better DX * Parent-child relationships should be obvious ❌ **Don't use slots when:** * Components are independent * There's no clear parent-child relationship * You're just grouping unrelated components ## Slots vs Children Slots don't restrict what children you can use. They're just a convenient way to access related components. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} Title {/* You can still use any content */}

Regular paragraph

Tip
``` Using better-styled with React Native # The core function for creating styled components Source: https://better-styled.com/concepts/styled The styled() function is the heart of better-styled. It takes any React component (or HTML element) and adds variant support. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; const Button = styled("button", { base: { className: "px-4 py-2 rounded" }, variants: { size: { sm: { className: "text-sm" }, lg: { className: "text-lg" }, }, }, }); ``` ## Configuration ### `base` Props that always apply, regardless of variants. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Card = styled("div", { base: { className: "rounded-xl border shadow-sm", role: "article", }, }); ``` Any valid prop for the component works here: `className`, `style`, event handlers, ARIA attributes, etc. ### `variants` Define your variant options. Each variant is a named group with multiple options. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: { className: "h-8 px-3 text-sm" }, md: { className: "h-10 px-4 text-base" }, lg: { className: "h-12 px-6 text-lg" }, }, intent: { primary: { className: "bg-blue-600 text-white" }, danger: { className: "bg-red-600 text-white" }, ghost: { className: "bg-transparent" }, }, }, }); // Usage ``` ### `defaultVariants` Set which variant values apply when not specified. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, defaultVariants: { size: "md", }, }); // These are equivalent: ``` ### className Classes are merged using `tailwind-merge`, which handles Tailwind conflicts intelligently. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { base: { className: "px-4 py-2 bg-blue-500" }, variants: { size: { lg: { className: "px-8 py-4" }, // px and py override, bg stays }, }, }); ``` ### style Style objects are merged with `Object.assign`. Later values override earlier ones. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Box = styled("div", { base: { style: { padding: 16, margin: 8 } }, variants: { spacing: { large: { style: { padding: 32 } }, // only padding overrides }, }, }); ``` ### Event Handlers Here's something special: event handlers are **composed**, not overwritten. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { base: { onClick: () => console.log("base click"), }, variants: { tracked: { true: { onClick: () => console.log("tracked click"), }, }, }, }); // Both handlers run! ``` This is powerful for analytics, logging, or any case where you want to add behavior without replacing it. ## Works With Any Component `styled()` works with HTML elements, React components, and React Native components. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // HTML element const Button = styled("button", { ... }); // React component const StyledLink = styled(Link, { ... }); // React Native const StyledPressable = styled(Pressable, { ... }); ``` The only requirement: the component must accept the props you define in your variants. ## TypeScript Types are fully inferred. No manual typing needed. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: {}, md: {}, lg: {}, }, }, }); // ✅ TypeScript knows: size?: "sm" | "md" | "lg" ``` ⚠️ Avoid naming variants after existing component props. Use `isDisabled` instead of `disabled` to prevent shadowing native props. With context, you can use the special `["boolean"]` syntax to get proper boolean type inference. See [Context](/concepts/context#boolean-variants) for details. ## Default Variants Set sensible defaults so users don't have to specify everything. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: { className: "h-8" }, md: { className: "h-10" }, lg: { className: "h-12" }, }, variant: { primary: { className: "bg-blue-600" }, secondary: { className: "bg-gray-200" }, }, }, defaultVariants: { size: "md", variant: "primary", }, }); // All of these render the same: // Rendered HTML: // // // Note: "size" doesn't appear in the DOM ``` This means you can use any variant name without worrying about HTML attribute conflicts. Learn how variants propagate from parent to child # Patterns and recommendations for better-styled Source: https://better-styled.com/guides/best-practices Recommended patterns, performance tips, and common pitfalls to avoid. ## Component Organization ### Single-File Pattern For compound components, define everything in one file: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Button.tsx import { styled, createStyledContext, withSlots } from "better-styled"; // 1. Context first const ButtonContext = createStyledContext({ size: ["sm", "md", "lg"], variant: ["solid", "outline", "ghost"], }); // 2. Root component const ButtonRoot = styled("button", { context: ButtonContext, base: { className: "inline-flex items-center gap-2 font-medium" }, variants: { size: { sm: { className: "h-8 px-3 text-sm" }, md: { className: "h-10 px-4 text-base" }, lg: { className: "h-12 px-6 text-lg" }, }, variant: { solid: { className: "bg-blue-600 text-white" }, outline: { className: "border-2 border-blue-600 text-blue-600" }, ghost: { className: "text-blue-600 hover:bg-blue-50" }, }, }, defaultVariants: { size: "md", variant: "solid", }, }); // 3. Slots const ButtonIcon = styled("span", { context: ButtonContext, variants: { size: { sm: { className: "w-4 h-4" }, md: { className: "w-5 h-5" }, lg: { className: "w-6 h-6" }, }, }, }); const ButtonLabel = styled("span", { context: ButtonContext, variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, }); // 4. Export compound component export const Button = withSlots(ButtonRoot, { Icon: ButtonIcon, Label: ButtonLabel, }); ``` *** ## Naming Conventions ### Variant Names Use descriptive, semantic names: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Good - describes what it does variants: { variant: { solid, outline, ghost, danger, "danger-soft" }, size: { sm, md, lg, xl }, isDisabled: { true: ... }, isLoading: { true: ... }, } // ❌ Avoid - too generic or unclear variants: { type: { a, b, c }, s: { 1, 2, 3 }, disabled: { true: ... }, // Shadows native prop } ``` ### Boolean Variants Prefix with `is` or `has` to avoid shadowing native props: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Good isDisabled, isLoading, isActive, hasIcon // ❌ Bad - shadows native props disabled, loading, active ``` ### Slot Names Use PascalCase, matching the visual role: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Good Button.Icon, Button.Label Card.Header, Card.Body, Card.Footer Dialog.Title, Dialog.Content, Dialog.Actions ``` *** ## Performance ### Avoid Inline Definitions Define styled components outside render: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Good - config is stable const Button = styled("button", { variants: { size: { sm: { className: "text-sm" } }, }, }); // ❌ Bad - recreates config every render const MyComponent = () => { const Button = styled("button", { variants: { size: { sm: { className: "text-sm" } }, }, }); return ``` ### Recommended Variant Options Common patterns for design systems: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Size size: ["xs", "sm", "md", "lg", "xl"] // Variant/Intent variant: ["solid", "outline", "ghost", "link"] // or with semantic colors intent: ["primary", "secondary", "success", "warning", "danger"] // Color (when separate from intent) color: ["neutral", "primary", "secondary", "success", "warning", "danger"] // Boolean states isDisabled, isLoading, isActive, isSelected, hasIcon ``` *** ## React Native Specific ### Use `isDisabled` Pattern ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // ✅ Good - works with Pressable const Button = styled(Pressable, { variants: { isDisabled: { true: { className: "opacity-50", disabled: true }, }, }, }); ``` ### Pressable States Leverage function composition for press states: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled(Pressable, { base: { className: ({ pressed }) => cn("px-4 py-2 rounded", pressed && "opacity-80"), }, }); ``` ### Platform-Specific Styles Use NativeWind's platform prefixes: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Card = styled(View, { base: { className: "p-4 rounded-xl ios:shadow-sm android:elevation-2", }, }); ``` Deep dive into type inference # Using better-styled with React Native and Expo Source: https://better-styled.com/guides/react-native The API is identical to web - just use React Native components instead of HTML elements. Works with NativeWind and Uniwind. ## Prerequisites better-styled uses `className` for styling. You need a Tailwind-to-StyleSheet solution: * [NativeWind](https://www.nativewind.dev/) - Tailwind CSS utilities for React Native * [Uniwind](https://uniwind.dev/) - High-performance Tailwind bindings for React Native (by the Unistyles team) Both libraries convert Tailwind class names to React Native styles. Follow their installation guides first - better-styled works with both out of the box. ## Basic Usage ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; import { Pressable, Text, View } from "react-native"; const Card = styled(View, { base: { className: "rounded-xl bg-white p-4 shadow-sm", }, }); const Button = styled(Pressable, { base: { className: "rounded-lg bg-blue-600 px-4 py-2 active:opacity-80", }, variants: { size: { sm: { className: "px-3 py-1.5" }, lg: { className: "px-6 py-3" }, }, }, }); const Label = styled(Text, { base: { className: "text-white font-medium text-center", }, }); ``` ## Pressable States Use Tailwind's state modifiers directly in your className: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled(Pressable, { base: { className: "bg-blue-600 rounded-lg px-4 py-2 active:opacity-80 active:scale-95", }, }); ``` No need for `style={({ pressed }) => ...}` - the className handles it cleanly. ## Platform-Specific Styles Use `ios:` and `android:` prefixes directly in className: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Card = styled(View, { base: { className: "rounded-xl bg-white p-4 ios:shadow-sm android:elevation-2", }, }); const Button = styled(Pressable, { base: { className: "ios:bg-blue-600 android:bg-blue-500", }, }); ``` ## Function Composition: Haptics Example One of better-styled's most powerful features is **function composition**. Event handlers from different sources (base, variants, direct props) all execute in sequence - they don't overwrite each other. This is perfect for things like haptic feedback: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; import { Pressable } from "react-native"; import * as Haptics from "expo-haptics"; const Button = styled(Pressable, { base: { className: "rounded-lg px-4 py-2 active:opacity-80", }, variants: { variant: { primary: { className: "bg-blue-600" }, danger: { className: "bg-red-600" }, }, haptics: { light: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light), }, medium: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium), }, heavy: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy), }, }, }, }); ``` Now you can add haptics to any button: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` **Execution order:** 1. ① Base `onPress` (if any) 2. ② Variant `onPress` (haptics feedback) 3. ③ Direct `onPress` prop (your handler) ✅ All three run. Nothing gets overwritten. This pattern is perfect for **design system features** like: * 📳 **Haptics** - Tactile feedback as part of component UX * 🔊 **Sound effects** - Audio feedback on interactions ## Full Example: Button Component A complete button component with context and slots: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext, styled, withSlots } from "better-styled"; import { Pressable, Text } from "react-native"; import * as Haptics from "expo-haptics"; const ButtonContext = createStyledContext({ variant: ["primary", "secondary", "outline"], size: ["sm", "md", "lg"], isDisabled: ["boolean"], }); const ButtonRoot = styled(Pressable, { context: ButtonContext, base: { className: "flex-row items-center justify-center rounded-xl active:opacity-80", }, variants: { variant: { primary: { className: "bg-blue-600" }, secondary: { className: "bg-gray-600" }, outline: { className: "bg-transparent border-2 border-blue-600" }, }, size: { sm: { className: "px-3 py-1.5 gap-1.5" }, md: { className: "px-4 py-2 gap-2" }, lg: { className: "px-6 py-3 gap-2.5" }, }, isDisabled: { true: { className: "opacity-50", disabled: true }, }, haptics: { light: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light), }, medium: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium), }, heavy: { onPress: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy), }, success: { onPress: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success), }, warning: { onPress: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning), }, error: { onPress: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error), }, }, }, defaultVariants: { variant: "primary", size: "md", }, }); const ButtonLabel = styled(Text, { context: ButtonContext, base: { className: "font-semibold" }, variants: { variant: { primary: { className: "text-white" }, secondary: { className: "text-white" }, outline: { className: "text-blue-600" }, }, size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, }); export const Button = withSlots(ButtonRoot, { Label: ButtonLabel, }); ``` Usage: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} {/* style prop overrides className styles */} ``` ## Tips `Pressable` is the modern way to handle touches in React Native. It's more flexible and has better TypeScript support. NativeWind and Uniwind support `active:`, `focus:`, `disabled:` and other state modifiers. Use them instead of managing state manually. Add haptics or sound effects at the variant level. They won't interfere with handlers passed directly to the component. Avoid naming variants after existing component props. For example, use `isDisabled` instead of `disabled` since `Pressable` already has a `disabled` prop. This prevents confusion and type conflicts. The `style` prop has higher priority than `className`. If you pass `style={{ backgroundColor: 'red' }}`, it will override any background color set via Tailwind classes. Get the most out of type inference # Getting the most out of type inference Source: https://better-styled.com/guides/typescript better-styled is built with TypeScript from the ground up. Types are inferred automatically - no manual typing needed. ## Automatic Inference When you define variants, TypeScript infers the types automatically. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, isDisabled: { true: { className: "opacity-50", disabled: true }, }, }, }); // TypeScript knows: // - size?: "sm" | "md" | "lg" // - isDisabled?: boolean
); } ``` ## Shared Configs When multiple components share the same variants and context, use `styledConfig()` to create a single typed config: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext, styled, styledConfig, withSlots } from "better-styled"; const ImageCtx = createStyledContext({ variant: ["solid", "bordered", "light"], }); // Single config validated against both components const config = styledConfig([UniwindImage, UniwindImageBg], { context: ImageCtx, variants: { variant: { solid: { className: "bg-black" }, bordered: { className: "border-2" }, light: { className: "bg-gray-100" }, }, }, }); const StyledImage = styled(UniwindImage, config); const StyledImageBg = styled(UniwindImageBg, config); ``` `styledConfig()` uses the same type inference as `styled()` — no generics needed. TypeScript validates that the config is compatible with **both** components simultaneously. `styledConfig()` is an identity function — it returns the config unchanged. It exists purely for type inference, with zero runtime cost. ## Extending Components When you wrap a styled component, types flow through: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const BaseButton = styled("button", { variants: { size: { sm: {}, lg: {} }, }, }); // Types are preserved function IconButton(props: React.ComponentProps & { icon: string }) { const { icon, ...rest } = props; return ( {icon} {props.children} ); } Star ``` ## Strict Variants By default, variant props are optional. If you want to require them, don't use `defaultVariants`: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // size is required because no default const Button = styled("button", { variants: { size: { sm: { className: "text-sm" }, lg: { className: "text-lg" }, }, }, // No defaultVariants for size }); ; } ``` For React Native: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled(Pressable, { ... }); function Screen() { const buttonRef = useRef(null); return ; } ``` ## Generic Components If you need a component that works with multiple element types: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // This is advanced - usually you don't need this function createStyledLink(component: T) { return styled(component, { base: { className: "text-blue-600 hover:underline" }, }); } const InternalLink = createStyledLink(Link); const ExternalLink = createStyledLink("a"); ``` ## Common Patterns ### Omit Specific Variants ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { variants: { size: { sm: {}, lg: {} }, internal: { true: {} }, // Don't expose this }, }); type PublicButtonProps = Omit< React.ComponentProps, "internal" >; ``` ### Require Children ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Card = styled("div", { base: { className: "p-4 rounded" }, }); type CardProps = React.ComponentProps & { children: React.ReactNode; // Make children required }; function StrictCard({ children, ...props }: CardProps) { return {children}; } ``` ## Tips Don't add type annotations unless necessary. The inference is designed to work without them. Always pass arrays directly to `createStyledContext`. Don't assign them to variables first, or you'll lose the literal types. Hover over components in your IDE to see the inferred types. This is the best way to understand what TypeScript sees. Complete API documentation # Install better-styled in your project Source: https://better-styled.com/installation Get started with better-styled in under a minute. Works with React, React Native, Expo, Next.js, Vite, and more. ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} npm install better-styled ``` ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} yarn add better-styled ``` ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} pnpm add better-styled ``` ```bash theme={"theme":{"light":"min-light","dark":"synthwave-84"}} bun add better-styled ``` ## Requirements * ⚛️ React 18+ or React Native * 🎨 Tailwind CSS (required) * 📘 TypeScript 5.0+ (recommended) ## Tailwind CSS Setup better-styled uses `className` for styling. You need Tailwind CSS configured in your project. Follow the [Tailwind CSS installation guide](https://tailwindcss.com/docs/installation) for your framework (Next.js, Vite, etc.). You need a Tailwind-to-StyleSheet solution: * [NativeWind](https://www.nativewind.dev/) - Tailwind CSS utilities for React Native * [Uniwind](https://uniwind.dev/) - High-performance Tailwind bindings for React Native Follow their installation guides first. ## That's it ✨ ✅ No extra configuration. No providers to wrap. No build plugins. Just import and use: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; ``` ## Smart Class Merging better-styled uses `tailwind-merge` under the hood to handle class conflicts intelligently: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { base: { className: "px-4 py-2 rounded" }, variants: { size: { sm: { className: "px-2 py-1 text-sm" }, // overrides px-4 py-2 lg: { className: "px-6 py-3 text-lg" }, // overrides px-4 py-2 }, }, }); ``` Build your first component Using with Expo and React Native # Introduction Source: https://better-styled.com/introduction Type-safe styled components with variant propagation for React and React Native. A minimalist approach to building consistent UI components. Full TypeScript inference without manual typing or `as const` Variants automatically flow from parent to child components Build complex components with the slots pattern Same API for React Web and React Native ## The Problem When building UI components, you often need child elements to match their parent's style. A button's text should match the button's size. A card's header should match the card's variant. The typical solution? Prop drilling. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // Without better-styled: manual prop drilling ``` This gets tedious. And error-prone. ## The Solution With better-styled, you define the relationship once. Children inherit automatically. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} // With better-styled: automatic inheritance ``` Both `Button.Icon` and `Button.Label` know they should render as `size="lg"` and `variant="primary"`. No repetition needed. ## Quick Example ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { createStyledContext, styled, withSlots } from "better-styled"; import { Pressable, Text } from "react-native"; // 1. Define your variants const ButtonContext = createStyledContext({ size: ["sm", "md", "lg"], variant: ["primary", "secondary"], }); // 2. Create the parent component const ButtonRoot = styled(Pressable, { context: ButtonContext, base: { className: "rounded-lg items-center justify-center" }, variants: { size: { sm: { className: "px-3 py-1.5" }, md: { className: "px-4 py-2" }, lg: { className: "px-6 py-3" }, }, variant: { primary: { className: "bg-blue-600" }, secondary: { className: "bg-gray-600" }, }, }, }); // 3. Create child that inherits from context const ButtonLabel = styled(Text, { context: ButtonContext, base: { className: "font-medium text-white" }, variants: { size: { sm: { className: "text-sm" }, md: { className: "text-base" }, lg: { className: "text-lg" }, }, }, }); // 4. Export as compound component export const Button = withSlots(ButtonRoot, { Label: ButtonLabel, }); ``` Now use it: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` The label automatically gets `size="lg"` from the parent. Clean and maintainable. ## Why better-styled? | Feature | tailwind-variants | better-styled | | ------------------- | ----------------- | ------------- | | Variants | ✅ | ✅ | | Compound Variants | ✅ | ✅ | | Context Propagation | ❌ | ✅ | | Compound Components | ❌ | ✅ | | Type Inference | Needs `as const` | ✅ Automatic | | React Native | ❌ | ✅ | ## Next Steps Get started in under a minute Build your first component Using with Expo and React Native Get the most out of type inference # Build your first component with better-styled Source: https://better-styled.com/quickstart Learn the three core concepts: styled(), createStyledContext(), and withSlots(). Let's build a Button component from scratch. You'll learn the three core concepts: 1. `styled()` - create components with variants 2. `createStyledContext()` - share variants between components 3. `withSlots()` - compose compound components ## Step 1: A Simple Styled Component Start with the basics. Create a button with size variants. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} import { styled } from "better-styled"; const Button = styled("button", { base: { className: "font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700", }, variants: { size: { sm: { className: "px-3 py-1.5 text-sm" }, md: { className: "px-4 py-2 text-base" }, lg: { className: "px-6 py-3 text-lg" }, }, }, defaultVariants: { size: "md", }, }); ``` Use it: ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` That's the foundation. `base` applies always, `variants` apply based on props. ## Step 2: Add More Variants Let's add a `variant` prop for different styles. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { base: { className: "font-medium rounded-lg transition-colors", }, variants: { variant: { primary: { className: "bg-blue-600 text-white hover:bg-blue-700" }, secondary: { className: "bg-gray-200 text-gray-900 hover:bg-gray-300" }, ghost: { className: "bg-transparent text-gray-700 hover:bg-gray-100" }, }, size: { sm: { className: "px-3 py-1.5 text-sm" }, md: { className: "px-4 py-2 text-base" }, lg: { className: "px-6 py-3 text-lg" }, }, }, defaultVariants: { variant: "primary", size: "md", }, }); ``` ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} ``` ## Step 3: Compound Variants Sometimes you need special styling when multiple variants combine. That's what `compoundVariants` is for. ```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}} const Button = styled("button", { base: { className: "font-medium rounded-lg transition-colors", }, variants: { variant: { primary: { className: "bg-blue-600 text-white" }, secondary: { className: "bg-gray-200 text-gray-900" }, }, size: { sm: { className: "px-3 py-1.5 text-sm" }, lg: { className: "px-6 py-3 text-lg" }, }, }, compoundVariants: [ { variant: "primary", size: "lg", props: { className: "shadow-lg shadow-blue-500/30" }, }, ], }); ``` Now ` ``` `Button.Label` automatically receives `size="lg"` from the parent. No prop drilling. ## What You've Learned 1. ✅ **`styled(component, config)`** - Creates components with variant support 2. ✅ **`createStyledContext(variants)`** - Defines which variants should be shared 3. ✅ **`withSlots(component, slots)`** - Creates compound components with dot notation ## Next Steps Deep dive into the main API Learn about compound variants and defaults Master parent-child communication Build complex component APIs