home / skills / gluestack / agent-skills / styling
This skill helps you implement Gluestack UI v4 styling patterns with semantic tokens, dark mode, spacing, and variant management.
npx playbooks add skill gluestack/agent-skills --skill stylingReview the files below or copy the command above to add this skill to your agents.
---
name: gluestack-ui-v4:styling
description: Styling patterns for gluestack-ui v4 - covers semantic tokens, spacing, dark mode, variants with tva, and className merging.
---
# Gluestack UI v4 - Styling Patterns
This sub-skill focuses on styling patterns, theming, colors, spacing, dark mode, and variant management for gluestack-ui v4.
## Rule 3: Semantic Color Tokens Over Raw Values (v4)
**CRITICAL**: You MUST use only Gluestack v4 semantic tokens. Generic tokens like `typography-*`, `neutral-*`, `gray-*`, `slate-*`, or any numbered color tokens (`red-500`, `blue-600`, etc.) are **STRICTLY PROHIBITED**.
### Prohibited Token Patterns
**NEVER use these token patterns:**
| Prohibited Pattern | Why It's Wrong | Use Instead |
| ------------------ | -------------- | ----------- |
| `typography-*` | Generic, not semantic | `text-foreground`, `text-muted-foreground`, `text-card-foreground` |
| `neutral-*` | Generic, not semantic | `text-foreground`, `bg-background`, `bg-muted` |
| `gray-*`, `slate-*` | Raw color, not semantic | `text-muted-foreground`, `bg-muted`, `border-border` |
| `text-red-500`, `bg-red-600` | Numbered colors, not semantic | `text-destructive`, `bg-destructive` |
| `text-green-500`, `bg-green-600` | Numbered colors, not semantic | `text-primary` (for success states) |
| `text-blue-500`, `bg-blue-600` | Numbered colors, not semantic | `text-primary`, `bg-primary` |
| `border-gray-200` | Raw color, not semantic | `border-border` |
| `#DC2626`, `#3b82f6` (inline) | Hex values, not semantic | `text-destructive`, `bg-primary` |
| `bg-white`, `bg-black` | Raw colors, not semantic | `bg-background`, `text-foreground` |
| `text-opacity-*` | Opacity utilities | Use alpha values: `text-foreground/70` |
### Correct Semantic Token Replacement Guide
Use Gluestack v4 semantic tokens instead of raw Tailwind colors or arbitrary values:
| Instead of | Use |
| ---------- | --- |
| text-red-500 | text-destructive |
| text-green-500 | text-primary (for success) |
| text-blue-500 | text-primary |
| text-gray-500, text-neutral-500 | text-muted-foreground |
| text-gray-900, text-typography-900 | text-foreground |
| bg-blue-600 | bg-primary |
| bg-gray-100, bg-neutral-100 | bg-muted |
| bg-gray-50 | bg-background |
| border-gray-200, border-neutral-200 | border-border |
| #DC2626 (inline) | text-destructive |
| bg-white | bg-background |
| text-black | text-foreground |
### Available Semantic Token Categories (v4)
| Token | Purpose | Usage Example |
| -------------------- | ---------------------------------------- | ------------------------------------ |
| primary | Brand identity, key interactive elements | `bg-primary`, `text-primary` |
| primary-foreground | Text on primary backgrounds | `text-primary-foreground` |
| secondary | Secondary actions, supporting elements | `bg-secondary` |
| secondary-foreground | Text on secondary backgrounds | `text-secondary-foreground` |
| background | Main background color | `bg-background` |
| foreground | Main text color | `text-foreground` |
| card | Card backgrounds | `bg-card` |
| card-foreground | Text on card backgrounds | `text-card-foreground` |
| popover | Popover/modal backgrounds | `bg-popover` |
| popover-foreground | Text on popover backgrounds | `text-popover-foreground` |
| muted | Muted backgrounds | `bg-muted` |
| muted-foreground | Muted text color | `text-muted-foreground` |
| destructive | Error states, destructive actions | `bg-destructive`, `text-destructive` |
| border | Border colors | `border-border` |
| input | Input border colors | `border-input` |
| ring | Focus ring colors | `ring-ring` |
| accent | Accent highlights | `bg-accent` |
| accent-foreground | Text on accent backgrounds | `text-accent-foreground` |
### CRITICAL: Why Semantic Tokens Are Mandatory
**Semantic tokens are NOT optional**. They are required for:
1. **Theme Consistency** - Tokens automatically adapt to light/dark modes
2. **Maintainability** - Change theme once, update everywhere
3. **Intent Expression** - `text-destructive` communicates purpose, `text-red-500` doesn't
4. **Future-Proofing** - Theme changes don't require code updates
5. **Accessibility** - Tokens ensure proper contrast ratios
**If you use generic tokens (`typography-*`, `neutral-*`) or numbered colors (`gray-500`, `blue-600`), the component will:**
- Break in dark mode
- Fail to match the design system
- Create maintenance debt
- Violate gluestack-ui v4 design principles
### Alpha Values
All color tokens support alpha values using the `/` syntax:
```tsx
// ✅ CORRECT: Using alpha values with semantic tokens
<Box className="bg-primary/90" />
<Text className="text-foreground/70" />
<Box className="border-border/80" />
// ❌ INCORRECT: Never use opacity utilities
<Text className="text-gray-900 opacity-70" />
<Box className="bg-blue-500 bg-opacity-90" />
// ✅ CORRECT: Alpha values, not opacity utilities
<Text className="text-foreground/70" />
<Box className="bg-primary/90" />
```
### Correct Pattern
```tsx
<Box className="bg-destructive">
<Text className="text-white">Error message</Text>
</Box>
<Box className="border border-border bg-card">
<Text className="text-card-foreground">Success!</Text>
</Box>
<Box className="bg-primary/90">
<Text className="text-primary-foreground">Primary action</Text>
</Box>
```
### Incorrect Pattern
```tsx
// ❌ PROHIBITED: Numbered color tokens
<Box className="bg-red-500">
<Text className="text-white">Error message</Text>
</Box>
// ❌ PROHIBITED: Inline hex values
<Box style={{ backgroundColor: '#DC2626' }}>
<Text style={{ color: 'green' }}>Success!</Text>
</Box>
// ❌ PROHIBITED: Arbitrary color values
<Box className="bg-[#3b82f6]">
<Text className="text-[#ffffff]">Primary action</Text>
</Box>
// ❌ PROHIBITED: Generic tokens (typography, neutral)
<Text className="text-typography-900">Heading</Text>
<Box className="bg-neutral-100">
<Text className="text-neutral-600">Description</Text>
</Box>
// ❌ PROHIBITED: Gray/Slate color scales
<Text className="text-gray-700">Content</Text>
<Box className="bg-slate-100 border-gray-300">
<Text className="text-gray-900">Text</Text>
</Box>
// ❌ PROHIBITED: Opacity utilities instead of alpha
<Text className="text-black opacity-70">Muted text</Text>
<Box className="bg-blue-600 bg-opacity-90">Content</Box>
```
## Rule 3: No Inline Styles
Avoid inline `style` props when className can achieve the same result.
### Resolution Hierarchy (in order of preference)
1. **className utilities** - Use existing Tailwind/NativeWind classes
2. **Gluestack component variants** - Use built-in component variants
3. **tva (Tailwind Variant Authority)** - Create reusable variant patterns
4. **NativeWind interop** - Enable className on third-party components
5. **Inline styles** - Only as absolute last resort with documented justification
### Correct Pattern
```tsx
<Box className="w-20 h-20 rounded-full bg-background" />
<Text size="lg" bold className="text-foreground" />
<Button variant="outline" size="lg">
<ButtonText>Click Me</ButtonText>
</Button>
```
### Incorrect Pattern
```tsx
<Box
style={{
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: "rgba(255, 255, 255, 0.1)",
}}
/>
<Text style={{ fontSize: 18, fontWeight: "bold", color: "#000" }} />
```
### Acceptable Exceptions
Inline styles are acceptable for:
1. **Dynamic values** - Values computed at runtime (e.g., animation values, safe area insets)
2. **Third-party component requirements** - Components that don't support className
3. **Platform-specific overrides** - When Platform.select is needed
```tsx
// Acceptable: dynamic value from hook
<Box style={{ paddingBottom: bottomInset }} />
// Acceptable: animation value
<Animated.View style={{ transform: [{ translateX: animatedValue }] }} />
// Acceptable: platform-specific
<Box style={Platform.select({ ios: { paddingTop: 20 }, android: {} })} />
```
## Rule 4: Spacing Scale Adherence
Use only values from the standard spacing scale. Arbitrary values create maintenance burden.
### Allowed Spacing Values
| Class | Size |
| ----- | ----- |
| 0 | 0px |
| 0.5 | 2px |
| 1 | 4px |
| 1.5 | 6px |
| 2 | 8px |
| 2.5 | 10px |
| 3 | 12px |
| 3.5 | 14px |
| 4 | 16px |
| 5 | 20px |
| 6 | 24px |
| 7 | 28px |
| 8 | 32px |
| 9 | 36px |
| 10 | 40px |
| 11 | 44px |
| 12 | 48px |
| 14 | 56px |
| 16 | 64px |
| 20 | 80px |
| 24 | 96px |
| 28 | 112px |
| 32 | 128px |
| 36 | 144px |
| 40 | 160px |
| 44 | 176px |
| 48 | 192px |
| 52 | 208px |
| 56 | 224px |
| 60 | 240px |
| 64 | 256px |
| 72 | 288px |
| 80 | 320px |
| 96 | 384px |
### Prohibited Patterns
- Arbitrary values: `p-[13px]`, `m-[27px]`, `gap-[15px]`
- Non-scale decimals: `p-2.7`, `m-4.3`
### Correct Pattern
```tsx
<Box className="p-4 m-2 gap-3" />
<Box className="px-6 py-4 mt-8" />
```
### Incorrect Pattern
```tsx
<Box className="p-[13px] m-[27px]" />
<Box style={{ padding: 13, margin: 27 }} />
```
## Rule 5: Dark Mode Using CSS Variables
Use the `dark:` prefix for dark mode support. Gluestack v4 uses CSS variables that automatically adapt to light/dark themes.
### Correct Pattern
```tsx
<Box className="bg-background dark:bg-background" />
<Text className="text-foreground dark:text-foreground" />
<Box className="border border-border dark:border-border" />
```
### Component-Level Dark Mode
```tsx
const CardView = ({ isDark }: { readonly isDark: boolean }) => (
<Box className={isDark ? "bg-card" : "bg-background"}>
<Text className={isDark ? "text-card-foreground" : "text-foreground"}>
Content
</Text>
</Box>
);
```
### Using Data Attributes for States
Gluestack components use data attributes for interactive states. These are automatically applied by the components based on user interaction:
| State Prop | Data Attribute | Usage in className |
| -------------- | -------------------- | ---------------------------------------- |
| `hover` | `data-hover` | `data-[hover=true]:bg-primary/90` |
| `active` | `data-active` | `data-[active=true]:bg-primary/80` |
| `disabled` | `data-disabled` | `data-[disabled=true]:opacity-50` |
| `focusVisible` | `data-focus-visible` | `data-[focus-visible=true]:ring-2` |
| `focus` | `data-focus` | `data-[focus=true]:border-ring` |
| `invalid` | `data-invalid` | `data-[invalid=true]:border-destructive` |
| `checked` | `data-checked` | `data-[checked=true]:bg-primary` |
```tsx
// Correct: Using data attributes for states in tva
const buttonStyle = tva({
base: 'rounded-md',
variants: {
variant: {
default: 'bg-primary data-[hover=true]:bg-primary/90 data-[active=true]:bg-primary/80',
destructive: 'bg-destructive data-[hover=true]:bg-destructive/90',
},
},
});
// Correct: Data attributes work automatically with component props
<Button disabled={isLoading}>
<ButtonText>Submit</ButtonText>
</Button>
<Input isInvalid={hasError}>
<InputField placeholder="Email" />
</Input>
```
## Rule 7: Variant-Based Styling with tva
For components with multiple style variants, use `tva` (Tailwind Variant Authority).
### Correct Pattern
```tsx
import { tva } from "@gluestack-ui/utils/nativewind-utils";
const cardStyles = tva({
base: "rounded-lg p-4",
variants: {
variant: {
default: "bg-card border border-border shadow-sm",
elevated: "bg-card shadow-hard-2",
outlined: "bg-transparent border border-border",
filled: "bg-muted",
},
size: {
sm: "p-2",
md: "p-4",
lg: "p-6",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
});
const Card = ({ variant, size, className }: CardProps) => (
<Box className={cardStyles({ variant, size, class: className })} />
);
```
### Using Parent Variants
For sub-components that inherit parent styles:
```tsx
const buttonTextStyle = tva({
base: "font-sans",
parentVariants: {
variant: {
default: "text-primary-foreground",
destructive: "text-white",
outline: "text-foreground",
},
size: {
sm: "text-xs",
md: "text-sm",
lg: "text-base",
},
},
});
```
## Rule 8: className Merging for Custom Components
Allow className override in custom components using the `class` parameter in tva.
### Correct Pattern
```tsx
interface BoxCardProps {
readonly className?: string;
readonly children: React.ReactNode;
}
const BoxCard = ({ className, children }: BoxCardProps) => {
const cardStyles = tva({
base: "rounded-lg bg-card p-4",
});
return <Box className={cardStyles({ class: className })}>{children}</Box>;
};
```
### Using withStyleContext for Parent-Child Communication
For components that need to share context with children:
```tsx
import {
withStyleContext,
useStyleContext,
} from "@gluestack-ui/utils/nativewind-utils";
const SCOPE = "CUSTOM_COMPONENT";
const Root = withStyleContext(View, SCOPE);
const Parent = ({ variant, children }) => (
<Root context={{ variant }} className={parentStyles({ variant })}>
{children}
</Root>
);
const Child = () => {
const { variant } = useStyleContext(SCOPE);
return <Box className={childStyles({ parentVariants: { variant } })} />;
};
```
## Layout Components Pattern
```tsx
// ✅ CORRECT: Using space prop instead of gap className
<VStack space="lg" className="p-4">
<HStack space="md" className="items-center justify-between">
<Heading size="xl">Title</Heading>
<Button variant="default" size="sm">
<ButtonText>Action</ButtonText>
</Button>
</HStack>
<Box className="bg-card rounded-lg p-4">
<Text size="md">Content</Text>
</Box>
</VStack>
```
## Anti-Patterns to Avoid
### ❌ Don't: Use Generic or Non-Semantic Tokens
**STRICTLY PROHIBITED** - These token patterns are never allowed:
```tsx
// ❌ PROHIBITED: Generic typography tokens
<Text className="text-typography-900">Heading</Text>
<Text className="text-typography-700">Body</Text>
<Text className="text-typography-500">Muted</Text>
// ✅ CORRECT: Semantic tokens
<Text className="text-foreground">Heading</Text>
<Text className="text-foreground">Body</Text>
<Text className="text-muted-foreground">Muted</Text>
// ❌ PROHIBITED: Neutral color tokens
<Box className="bg-neutral-100" />
<Text className="text-neutral-600" />
<Box className="border-neutral-300" />
// ✅ CORRECT: Semantic tokens
<Box className="bg-muted" />
<Text className="text-muted-foreground" />
<Box className="border-border" />
// ❌ PROHIBITED: Gray/Slate color scales
<Box className="bg-gray-50" />
<Text className="text-gray-900" />
<Box className="border-gray-200" />
<Text className="text-slate-700" />
// ✅ CORRECT: Semantic tokens
<Box className="bg-background" />
<Text className="text-foreground" />
<Box className="border-border" />
<Text className="text-foreground" />
// ❌ PROHIBITED: Numbered color tokens
<Box className="bg-blue-600" />
<Text className="text-red-500" />
<Box className="border-green-400" />
// ✅ CORRECT: Semantic tokens
<Box className="bg-primary" />
<Text className="text-destructive" />
<Box className="border-primary" />
```
### ❌ Don't: Use Raw Color Values
```tsx
// ❌ PROHIBITED: Arbitrary hex values
<Box className="bg-[#3b82f6]" />
<Text className="text-[#DC2626]" />
<Box style={{ backgroundColor: '#f3f4f6' }} />
// ✅ CORRECT: Semantic tokens
<Box className="bg-primary" />
<Text className="text-destructive" />
<Box className="bg-muted" />
// ❌ PROHIBITED: Named colors
<Text style={{ color: 'red' }} />
<Box style={{ backgroundColor: 'white' }} />
// ✅ CORRECT: Semantic tokens
<Text className="text-destructive" />
<Box className="bg-background" />
```
### ❌ Don't: Use Inline Styles When className Works
```tsx
// ❌ Incorrect
<Box style={{ padding: 16, backgroundColor: '#fff' }} />
// ✅ Correct
<Box className="p-4 bg-background" />
```
### ❌ Don't: Use Arbitrary Spacing Values
```tsx
// ❌ Incorrect
<Box className="p-[13px] m-[27px]" />
// ✅ Correct
<Box className="p-3 m-6" />
```
## Common Styling Patterns
### Card with Semantic Tokens
```tsx
<Box className="bg-card rounded-lg border border-border p-4 shadow-sm">
<Heading size="lg" className="text-card-foreground">
Title
</Heading>
<Text size="sm" className="text-muted-foreground">
Description
</Text>
</Box>
```
### Button Variants with tva
```tsx
const buttonStyles = tva({
base: "rounded-md px-4 py-2",
variants: {
variant: {
default: "bg-primary text-primary-foreground",
destructive: "bg-destructive text-white",
outline: "border border-border bg-transparent text-foreground",
ghost: "bg-transparent text-foreground",
},
},
});
<Button className={buttonStyles({ variant: "outline" })}>
<ButtonText>Click Me</ButtonText>
</Button>
```
### Dark Mode Support
```tsx
<Box className="bg-background dark:bg-background">
<Text className="text-foreground dark:text-foreground">
Adaptive content
</Text>
<Box className="border border-border dark:border-border">
<Text className="text-muted-foreground dark:text-muted-foreground">
Muted text
</Text>
</Box>
</Box>
```
## Token Usage Validation
### How to Validate Token Usage
Before committing any code, verify that you're using ONLY semantic tokens:
**✅ ALLOWED Token Patterns:**
- `text-foreground`, `text-muted-foreground`, `text-card-foreground`
- `text-primary`, `text-primary-foreground`
- `text-secondary`, `text-secondary-foreground`
- `text-destructive`, `text-accent`, `text-accent-foreground`
- `bg-background`, `bg-card`, `bg-muted`, `bg-popover`
- `bg-primary`, `bg-secondary`, `bg-destructive`, `bg-accent`
- `border-border`, `border-input`, `ring-ring`
- Alpha values: `text-foreground/70`, `bg-primary/90`, `border-border/50`
**❌ PROHIBITED Token Patterns:**
- ❌ `typography-*` (typography-900, typography-700, etc.)
- ❌ `neutral-*` (neutral-100, neutral-600, etc.)
- ❌ `gray-*` (gray-50, gray-900, etc.)
- ❌ `slate-*` (slate-700, slate-200, etc.)
- ❌ Numbered colors: `red-500`, `blue-600`, `green-400`
- ❌ Arbitrary values: `[#3b82f6]`, `[#DC2626]`
- ❌ Named colors: `'red'`, `'white'`, `'black'`
- ❌ Opacity utilities: `opacity-70`, `bg-opacity-90`
### Token Validation Checklist
Before submitting code, verify:
- [ ] **No `typography-*` tokens** - Replace with `text-foreground` or `text-muted-foreground`
- [ ] **No `neutral-*` tokens** - Replace with semantic equivalents
- [ ] **No `gray-*` or `slate-*` tokens** - Replace with semantic equivalents
- [ ] **No numbered color tokens** - Replace with semantic tokens
- [ ] **No arbitrary color values** - Replace with semantic tokens
- [ ] **No inline style colors** - Use className with semantic tokens
- [ ] **No opacity utilities** - Use alpha values instead (`/70`, `/90`)
- [ ] **All colors are semantic** - Every color token expresses intent
- [ ] **Dark mode compatible** - Semantic tokens work in both themes
- [ ] **Reviewed token table** - Verified against available semantic tokens list
### Common Token Violations and Fixes
```tsx
// ❌ VIOLATION: typography tokens
<Text className="text-typography-900">Title</Text>
<Text className="text-typography-600">Description</Text>
// ✅ FIX: Use semantic tokens
<Text className="text-foreground">Title</Text>
<Text className="text-muted-foreground">Description</Text>
// ❌ VIOLATION: neutral tokens
<Box className="bg-neutral-50 border-neutral-200">
<Text className="text-neutral-700">Content</Text>
</Box>
// ✅ FIX: Use semantic tokens
<Box className="bg-background border-border">
<Text className="text-foreground">Content</Text>
</Box>
// ❌ VIOLATION: gray scale tokens
<Box className="bg-gray-100">
<Text className="text-gray-900">Heading</Text>
<Text className="text-gray-500">Subtitle</Text>
</Box>
// ✅ FIX: Use semantic tokens
<Box className="bg-muted">
<Text className="text-foreground">Heading</Text>
<Text className="text-muted-foreground">Subtitle</Text>
</Box>
// ❌ VIOLATION: Numbered colors with opacity
<Text className="text-blue-600 opacity-70">Link</Text>
<Box className="bg-red-500 bg-opacity-10">Alert</Box>
// ✅ FIX: Use semantic tokens with alpha
<Text className="text-primary/70">Link</Text>
<Box className="bg-destructive/10">Alert</Box>
```
## Escalation Guidance
When a design request cannot be satisfied with existing patterns:
1. **Push back early** - Explain performance and maintenance implications
2. **Propose alternatives** - Map to existing tokens or suggest new semantic tokens
3. **Add to design system** - If truly needed, add token to `gluestack-ui-provider/config.ts`
4. **Document exception** - If inline style is unavoidable, add JSDoc explaining why
## Reference
- **Theme Configuration**: `@/components/ui/gluestack-ui-provider/config.ts`
- **Tailwind Config**: `./tailwind.config.js`
- **Documentation**: https://v4.gluestack.io/ui/docs
This skill documents styling patterns for Gluestack UI v4. It enforces semantic tokens, spacing scale, dark-mode practices, tva-based variants, and safe className merging for custom components to ensure consistent, theme-aware UI styling.
The skill inspects component className usage and style choices, recommending semantic token replacements and forbidding raw color tokens, hex values, and opacity utilities. It validates spacing classes against the approved scale, enforces className-first styling over inline styles, and guides variant creation with tva and parent-child style contexts. It also explains data-attribute use for interactive states and alpha value syntax for tokens.
Why are numbered color tokens and hex values prohibited?
They break theme consistency and dark mode. Semantic tokens express intent and adapt automatically to theme changes and accessibility requirements.
When are inline styles acceptable?
Only for dynamic runtime values (animations, safe area insets), third-party components that lack className, or platform-specific overrides with clear justification.