gluestack-ui-v4styling
Styling patterns for gluestack-ui v4 - covers semantic tokens, spacing, dark mode, variants with tva, and className merging.
SKILL.md
| Name | gluestack-ui-v4styling |
| Description | Styling patterns for gluestack-ui v4 - covers semantic tokens, spacing, dark mode, variants with tva, and className merging. |
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:
- Theme Consistency - Tokens automatically adapt to light/dark modes
- Maintainability - Change theme once, update everywhere
- Intent Expression -
text-destructivecommunicates purpose,text-red-500doesn't - Future-Proofing - Theme changes don't require code updates
- 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:
// ✅ 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
<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
// ❌ 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)
- className utilities - Use existing Tailwind/NativeWind classes
- Gluestack component variants - Use built-in component variants
- tva (Tailwind Variant Authority) - Create reusable variant patterns
- NativeWind interop - Enable className on third-party components
- Inline styles - Only as absolute last resort with documented justification
Correct Pattern
<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
<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:
- Dynamic values - Values computed at runtime (e.g., animation values, safe area insets)
- Third-party component requirements - Components that don't support className
- Platform-specific overrides - When Platform.select is needed
// 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
<Box className="p-4 m-2 gap-3" />
<Box className="px-6 py-4 mt-8" />
Incorrect Pattern
<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
<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
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 |
// 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
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:
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
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:
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
// ✅ 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:
// ❌ 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
// ❌ 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
// ❌ Incorrect
<Box style={{ padding: 16, backgroundColor: '#fff' }} />
// ✅ Correct
<Box className="p-4 bg-background" />
❌ Don't: Use Arbitrary Spacing Values
// ❌ Incorrect
<Box className="p-[13px] m-[27px]" />
// ✅ Correct
<Box className="p-3 m-6" />
Common Styling Patterns
Card with Semantic Tokens
<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
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
<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-foregroundtext-primary,text-primary-foregroundtext-secondary,text-secondary-foregroundtext-destructive,text-accent,text-accent-foregroundbg-background,bg-card,bg-muted,bg-popoverbg-primary,bg-secondary,bg-destructive,bg-accentborder-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 withtext-foregroundortext-muted-foreground - No
neutral-*tokens - Replace with semantic equivalents - No
gray-*orslate-*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
// ❌ 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:
- Push back early - Explain performance and maintenance implications
- Propose alternatives - Map to existing tokens or suggest new semantic tokens
- Add to design system - If truly needed, add token to
gluestack-ui-provider/config.ts - 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