Install
$ agentstack add skill-gluestack-agent-skills-variants ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Gluestack UI v5 — Creating Component Variants
This sub-skill focuses on creating custom variants for existing gluestack-ui v5 components, allowing you to extend the design system with project-specific styling patterns while maintaining consistency and type safety.
When to Create a Variant
Create a new variant when:
- Repeating the same style combination - Multiple places use the same className pattern
- Project-specific design patterns - Brand-specific button styles, card types, etc.
- Conditional styling - Component appearance changes based on props
- Extending existing components - Adding new visual styles to Gluestack components
- Theme-specific variations - Different appearances for specific contexts
Don't create variants for:
- One-off custom styles (use className instead)
- Simple modifications (use existing props + className)
- Styles that should be in the global design system
Variant Creation Workflow
Step 1: Analyze the Component
Before creating a variant, understand:
- What's the base component? - Button, Card, Badge, etc.
- What visual states are needed? - Colors, sizes, borders, shadows
- Are there sub-components? - ButtonText, CardHeader, etc.
- What props should control variants? - variant, size, state props
- Should variants affect children? - Parent variants for sub-components
Step 2: Plan Variant Structure
Define your variant system:
// Example: Planning a Badge component with variants
{
variant: ['default', 'success', 'warning', 'error', 'info']
size: ['sm', 'md', 'lg']
shape: ['rounded', 'pill', 'square']
}
Step 3: Implement with tva
Use tva (Tailwind Variant Authority) to create type-safe, composable variants.
Creating Simple Variants
Template: Adding Variants to a Custom Component
import React from 'react';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { Box } from '@/components/ui/box';
import { Text } from '@/components/ui/text';
interface BadgeProps {
readonly variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
readonly size?: 'sm' | 'md' | 'lg';
readonly shape?: 'rounded' | 'pill' | 'square';
readonly className?: string;
readonly children: React.ReactNode;
}
// Define variant styles
const badgeStyles = tva({
base: 'inline-flex items-center justify-center font-medium',
variants: {
variant: {
default: 'bg-muted text-muted-foreground',
success: 'bg-primary/10 text-primary border border-primary/20',
warning: 'bg-accent/10 text-accent-foreground border border-accent/20',
error: 'bg-destructive/10 text-destructive border border-destructive/20',
info: 'bg-secondary/10 text-secondary-foreground border border-secondary/20',
},
size: {
sm: 'px-2 py-0.5 text-xs',
md: 'px-3 py-1 text-sm',
lg: 'px-4 py-1.5 text-base',
},
shape: {
rounded: 'rounded-md',
pill: 'rounded-full',
square: 'rounded-none',
},
},
defaultVariants: {
variant: 'default',
size: 'md',
shape: 'rounded',
},
});
export const Badge = ({
variant,
size,
shape,
className,
children
}: BadgeProps) => {
return (
{children}
);
};
// Usage:
// Active
// Error
Key Points:
- ✅ Uses
tvafor variant management - ✅ Base styles apply to all variants
- ✅ Multiple variant dimensions (variant, size, shape)
- ✅ Default variants specified
- ✅ className override support with
classparameter - ✅ TypeScript types for variant options
Extending Existing Gluestack Components
Template: Adding Custom Variants to Button
import React from 'react';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { Button as GluestackButton, ButtonText } from '@/components/ui/button';
// Define additional variant styles
const customButtonStyles = tva({
base: '',
variants: {
variant: {
// Extend existing variants with new ones
gradient: 'bg-gradient-to-r from-primary to-accent',
glass: 'bg-background/20 backdrop-blur-lg border border-border/50',
neon: 'bg-transparent border-2 border-primary shadow-[0_0_15px_rgba(59,130,246,0.5)]',
},
size: {
// Add custom sizes
xs: 'px-2 py-1',
xl: 'px-8 py-4',
},
},
});
interface CustomButtonProps {
readonly variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'gradient' | 'glass' | 'neon';
readonly size?: 'default' | 'sm' | 'lg' | 'icon' | 'xs' | 'xl';
readonly className?: string;
readonly onPress?: () => void;
readonly isDisabled?: boolean;
readonly children: React.ReactNode;
}
export const CustomButton = ({
variant = 'default',
size = 'default',
className,
onPress,
isDisabled,
children,
}: CustomButtonProps) => {
// Use Gluestack Button for built-in variants
if (['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'].includes(variant)) {
return (
{children}
);
}
// Use custom variants
return (
{children}
);
};
// Usage:
//
// Gradient Button
//
//
//
// Neon Button
//
Key Points:
- ✅ Extends existing component
- ✅ Preserves original variants
- ✅ Adds new custom variants
- ✅ Maintains compound component pattern
- ✅ Type-safe variant options
Parent-Child Variant Relationships
When creating components with sub-components, use parentVariants to style children based on parent state.
Template: Card with Variant-Aware Children
import React from 'react';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { Box } from '@/components/ui/box';
import { Heading } from '@/components/ui/heading';
import { Text } from '@/components/ui/text';
interface CardProps {
readonly variant?: 'default' | 'elevated' | 'outlined' | 'ghost';
readonly colorScheme?: 'neutral' | 'primary' | 'success' | 'error';
readonly className?: string;
readonly children: React.ReactNode;
}
interface CardHeaderProps {
readonly className?: string;
readonly children: React.ReactNode;
}
interface CardBodyProps {
readonly className?: string;
readonly children: React.ReactNode;
}
// Parent card styles
const cardStyles = tva({
base: 'rounded-lg overflow-hidden',
variants: {
variant: {
default: 'border border-border bg-card',
elevated: 'shadow-hard-2 bg-card',
outlined: 'border-2 border-border bg-transparent',
ghost: 'bg-transparent',
},
colorScheme: {
neutral: '',
primary: 'border-primary/20',
success: 'border-primary/20',
error: 'border-destructive/20',
},
},
compoundVariants: [
{
variant: 'default',
colorScheme: 'primary',
class: 'bg-primary/5',
},
{
variant: 'default',
colorScheme: 'success',
class: 'bg-primary/5',
},
{
variant: 'default',
colorScheme: 'error',
class: 'bg-destructive/5',
},
],
defaultVariants: {
variant: 'default',
colorScheme: 'neutral',
},
});
// Child styles that respond to parent variants
const cardHeaderStyles = tva({
base: 'p-4 border-b',
parentVariants: {
variant: {
default: 'border-border',
elevated: 'border-border/50',
outlined: 'border-border',
ghost: 'border-transparent',
},
colorScheme: {
neutral: '',
primary: 'border-primary/20 bg-primary/5',
success: 'border-primary/20 bg-primary/5',
error: 'border-destructive/20 bg-destructive/5',
},
},
});
const cardBodyStyles = tva({
base: 'p-4',
parentVariants: {
colorScheme: {
neutral: '',
primary: '',
success: '',
error: '',
},
},
});
// Context to share variant state with children
const CardContext = React.createContext>({
variant: 'default',
colorScheme: 'neutral',
});
export const Card = ({
variant = 'default',
colorScheme = 'neutral',
className,
children
}: CardProps) => {
return (
{children}
);
};
export const CardHeader = ({ className, children }: CardHeaderProps) => {
const { variant, colorScheme } = React.useContext(CardContext);
return (
{children}
);
};
export const CardBody = ({ className, children }: CardBodyProps) => {
const { variant, colorScheme } = React.useContext(CardContext);
return (
{children}
);
};
// Usage:
//
//
// Success Card
//
//
// This card responds to parent variants
//
//
Key Points:
- ✅ Parent context shares variant state
- ✅ Children use
parentVariantsto style based on parent - ✅ Compound variants for complex combinations
- ✅ Type-safe context usage
- ✅ Flexible composition
Compound Variants
Use compound variants when combinations of variant options need special styling.
Template: Button with Compound Variants
import React from 'react';
import { tva } from '@gluestack-ui/utils/nativewind-utils';
import { Button, ButtonText, ButtonIcon } from '@/components/ui/button';
import { Loader2Icon } from '@/components/ui/icon';
interface ActionButtonProps {
readonly variant?: 'solid' | 'outline' | 'ghost';
readonly colorScheme?: 'primary' | 'secondary' | 'destructive';
readonly size?: 'sm' | 'md' | 'lg';
readonly isLoading?: boolean;
readonly isDisabled?: boolean;
readonly className?: string;
readonly onPress?: () => void;
readonly children: React.ReactNode;
}
const actionButtonStyles = tva({
base: 'rounded-md font-medium transition-colors',
variants: {
variant: {
solid: '',
outline: 'border-2 bg-transparent',
ghost: 'bg-transparent',
},
colorScheme: {
primary: '',
secondary: '',
destructive: '',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
},
compoundVariants: [
// Solid + Primary
{
variant: 'solid',
colorScheme: 'primary',
class: 'bg-primary text-primary-foreground data-[hover=true]:bg-primary/90',
},
// Solid + Destructive
{
variant: 'solid',
colorScheme: 'destructive',
class: 'bg-destructive text-white data-[hover=true]:bg-destructive/90',
},
// Outline + Primary
{
variant: 'outline',
colorScheme: 'primary',
class: 'border-primary text-primary data-[hover=true]:bg-primary/10',
},
// Outline + Destructive
{
variant: 'outline',
colorScheme: 'destructive',
class: 'border-destructive text-destructive data-[hover=true]:bg-destructive/10',
},
// Ghost + Primary
{
variant: 'ghost',
colorScheme: 'primary',
class: 'text-primary data-[hover=true]:bg-primary/10',
},
// Ghost + Destructive
{
variant: 'ghost',
colorScheme: 'destructive',
class: 'text-destructive data-[hover=true]:bg-destructive/10',
},
],
defaultVariants: {
variant: 'solid',
colorScheme: 'primary',
size: 'md',
},
});
export const ActionButton = ({
variant,
colorScheme,
size,
isLoading = false,
isDisabled = false,
className,
onPress,
children,
}: ActionButtonProps) => {
return (
{isLoading && }
{children}
);
};
// Usage:
//
// Primary Action
//
//
//
// Delete
//
Key Points:
- ✅ Compound variants handle specific combinations
- ✅ Base variants provide defaults
- ✅ Hover states with data attributes
- ✅ Loading state integration
- ✅ Flexible variant combinations
Common Variant Patterns
Pattern 1: Status Badges
const statusBadgeStyles = tva({
base: 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold',
variants: {
status: {
active: 'bg-primary/10 text-primary',
inactive: 'bg-muted text-muted-foreground',
pending: 'bg-accent/10 text-accent-foreground',
completed: 'bg-primary/10 text-primary',
failed: 'bg-destructive/10 text-destructive',
},
},
defaultVariants: {
status: 'inactive',
},
});
// Usage:
//
// Active
//
Pattern 2: Alert Variants
const alertStyles = tva({
base: 'rounded-lg border p-4',
variants: {
severity: {
info: 'bg-secondary/10 border-secondary/20 text-secondary-foreground',
success: 'bg-primary/10 border-primary/20 text-primary',
warning: 'bg-accent/10 border-accent/20 text-accent-foreground',
error: 'bg-destructive/10 border-destructive/20 text-destructive',
},
},
defaultVariants: {
severity: 'info',
},
});
// Usage:
//
// Error message
//
Pattern 3: Interactive Card States
const interactiveCardStyles = tva({
base: 'rounded-lg border border-border p-4 transition-all cursor-pointer',
variants: {
state: {
default: 'bg-card data-[hover=true]:bg-muted/50',
selected: 'bg-primary/10 border-primary',
disabled: 'bg-muted opacity-60 cursor-not-allowed',
},
},
defaultVariants: {
state: 'default',
},
});
// Usage:
//
//
// Selected Card
//
//
Pattern 4: Size Variants with Consistent Ratios
const avatarStyles = tva({
base: 'rounded-full overflow-hidden',
variants: {
size: {
xs: 'w-6 h-6',
sm: 'w-8 h-8',
md: 'w-12 h-12',
lg: 'w-16 h-16',
xl: 'w-20 h-20',
'2xl': 'w-24 h-24',
},
},
defaultVariants: {
size: 'md',
},
});
// Usage:
//
Best Practices for Variants
✅ Do's
- Use semantic variant names
```tsx // ✅ GOOD: Semantic names variant: 'primary' | 'secondary' | 'destructive'
// ❌ BAD: Generic names variant: 'blue' | 'red' | 'green' ```
- Provide default variants
``tsx // ✅ GOOD: Always specify defaults defaultVariants: { variant: 'default', size: 'md', } ``
- Use compound variants for combinations
``tsx // ✅ GOOD: Handle specific combinations compoundVariants: [ { variant: 'outline', colorScheme: 'primary', class: 'border-primary text-primary', }, ] ``
- Keep variant dimensions focused
``tsx // ✅ GOOD: Clear separation variants: { variant: { ... }, // Visual style size: { ... }, // Size state: { ... }, // Interactive state } ``
- Use ONLY semantic tokens in variant styles - NO EXCEPTIONS
```tsx // ✅ CORRECT: Semantic tokens with alpha values success: 'bg-primary/10 text-primary border-primary/20' error: 'bg-destructive/10 text-destructive border-destructive/20' muted: 'bg-muted text-muted-foreground border-border'
// ❌ PROHIBITED: Numbered color tokens success: 'bg-green-100 text-green-800 border-green-200' error: 'bg-red-100 text-red-800 border-red-200'
// ❌ PROHIBITED: Generic tokens muted: 'bg-neutral-100 text-neutral-600 border-neutral-300' muted: 'bg-gray-100 text-gray-600 border-gray-300'
// ❌ PROHIBITED: Typography tokens text: 'text-typography-900' ```
❌ Don'ts
- Don't create too many variant dimensions
```tsx // ❌ BAD: Too many dimensions variants: { variant: { ... }, size: { ... }, color: { ... }, border: { ... }, shadow: { ... }, rounded: { ... }, }
// ✅ GOOD: Focused dimensions variants: { variant: { ... }, size: { ... }, } ```
- Don't mix concerns in variant names
```tsx // ❌ BAD: Mixing visual and semantic variant: 'primary' | 'large-primary' | 'small-secondary'
// ✅ GOOD: Separate dimensions variant: 'primary' | 'secondary' size: 'sm' | 'md' | 'lg' ```
- Don't duplicate existing component props
```tsx // ❌ BAD: Duplicating Button's variant prop const CustomButton = ({ variant, ... }: { variant: 'new1' | 'new2' })
// ✅ GOOD: Extend existing variants const CustomButton = ({ variant, ... }: { variant: 'default' | 'outline' | 'new1'
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: gluestack
- Source: gluestack/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.