> ## Documentation Index
> Fetch the complete documentation index at: https://better-styled.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Share variants between parent and child components

> 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
<Button size="lg">
  <Icon size="lg" />
  <span>Click me</span>
</Button>
```

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
<Button size="lg">
  <Button.Icon />
  <Button.Label>Click me</Button.Label>
</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"}}
<Button size="lg" variant="primary">
  <Button.Icon>★</Button.Icon>
  <Button.Label>Star</Button.Label>
</Button>

// 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>
  <Button.Label>Click me</Button.Label>
</Button>

// 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"}}
<Button size="lg">
  <Button.Icon />           {/* size="lg" from context */}
  <Button.Label size="sm">  {/* size="sm" override */}
    Small text in large button
  </Button.Label>
</Button>
```

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"}}
<Button isDisabled>        {/* Works */}
<Button isDisabled={true}> {/* Same */}
<Button isDisabled={false}> {/* Explicitly not disabled */}
```

<Note>
  ⚠️ Use `isDisabled` instead of `disabled` to avoid shadowing the native `disabled` prop on elements like `button` or `Pressable`.
</Note>

## Variant Priority

When multiple sources provide variant values, this is the priority order (highest wins):

1. 🥇 **Props passed directly** to the component
2. 🥈 **Context values** from parent
3. 🥉 **defaultVariants** in config

```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}}
const ButtonContext = createStyledContext({
  size: ["sm", "md", "lg"],
});

const ButtonLabel = styled("span", {
  context: ButtonContext,
  defaultVariants: {
    size: "md",  // Lowest priority
  },
});

<Button size="lg">           {/* Context provides size="lg" */}
  <Button.Label />           {/* Gets size="lg" from context */}
  <Button.Label size="sm" /> {/* Gets size="sm" from direct prop */}
</Button>
```

## 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
<Button haptics="heavy">
  <Button.Text haptics="heavy" />  {/* Text shouldn't have haptics */}
  <Button.Icon haptics="heavy" />  {/* Icon shouldn't have haptics */}
</Button>
```

### 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"}}
<Button variant="solid" haptics="light">
  <Button.Text>Click me</Button.Text>
</Button>

// 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
<Button haptics="heavy" onPress={() => console.log("clicked")}>
  Submit
</Button>
// → 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.

<Note>
  `styledConfig()` uses the same type inference as `styled()`. No generics needed — just pass the components array and the config.
</Note>

<Card title="styledConfig() API Reference" icon="arrow-right" href="/api/styled-config">
  Signature, parameters, and type safety details
</Card>

## Nested Contexts

If you nest components with the same context, the nearest parent wins.

```tsx theme={"theme":{"light":"min-light","dark":"synthwave-84"}}
<Button size="lg">
  <Button.Label>Large</Button.Label>

  <Button size="sm">  {/* New context scope */}
    <Button.Label>Small</Button.Label>
  </Button>
</Button>
```

## 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.

<Card title="Next: Slots" icon="arrow-right" href="/concepts/slots">
  Build compound components with dot notation
</Card>
