From lisa-expo
This skill enforces Gluestack UI v3 and NativeWind v4 design patterns for consistent, performant, and maintainable styling. It should be used when creating or reviewing components, fixing styling issues, or refactoring styles to follow the constrained design system.
npx claudepluginhub codyswanngt/lisa --plugin lisa-expoThis skill uses the workspace's default tool permissions.
This skill enforces constrained, opinionated styling patterns that reduce decision fatigue, improve performance, enable consistent theming, and limit the solution space to canonical patterns.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
This skill enforces constrained, opinionated styling patterns that reduce decision fatigue, improve performance, enable consistent theming, and limit the solution space to canonical patterns.
Always use Gluestack components instead of direct React Native imports:
| React Native | Gluestack Equivalent |
|---|---|
View from "react-native" | Box from "@/components/ui/box" |
Text from "react-native" | Text from "@/components/ui/text" |
TouchableOpacity from "react-native" | Pressable from "@/components/ui/pressable" |
ScrollView from "react-native" | ScrollView from "@/components/ui/scroll-view" |
Image from "react-native" | Image from "@/components/ui/image" |
TextInput from "react-native" | Input, InputField from "@/components/ui/input" |
FlatList from "react-native" | FlashList from "@shopify/flash-list" |
import { Box } from "@/components/ui/box";
import { Text } from "@/components/ui/text";
import { Pressable } from "@/components/ui/pressable";
const Component = () => (
<Box className="p-4">
<Text className="text-typography-900">Hello</Text>
<Pressable onPress={handlePress}>
<Text>Press Me</Text>
</Pressable>
</Box>
);
import { View, Text, TouchableOpacity } from "react-native";
const Component = () => (
<View style={{ padding: 16 }}>
<Text style={{ color: "#333" }}>Hello</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Press Me</Text>
</TouchableOpacity>
</View>
);
Use Gluestack semantic tokens instead of raw Tailwind colors:
| Instead of | Use |
|---|---|
text-red-500 | text-error-500 |
text-green-500 | text-success-500 |
text-yellow-500 | text-warning-500 |
text-blue-500 | text-info-500 |
text-gray-500 | text-typography-500 |
bg-blue-600 | bg-primary-600 |
border-gray-200 | border-outline-200 |
#DC2626 (inline) | text-error-600 |
| Token | Purpose | Scale |
|---|---|---|
primary-{0-950} | Brand identity, key interactive elements | 0-950 |
secondary-{0-950} | Secondary actions, supporting elements | 0-950 |
tertiary-{0-950} | Tertiary accents | 0-950 |
error-{0-950} | Validation errors, destructive actions | 0-950 |
success-{0-950} | Positive feedback, completions | 0-950 |
warning-{0-950} | Alerts, attention-required states | 0-950 |
info-{0-950} | Informational content | 0-950 |
typography-{0-950} | Text colors | 0-950 |
outline-{0-950} | Border colors | 0-950 |
background-{0-950} | Background colors | 0-950 |
Use state-based background tokens:
bg-background-error - Error state backgroundsbg-background-warning - Warning state backgroundsbg-background-success - Success state backgroundsbg-background-muted - Muted/disabled backgroundsbg-background-info - Informational backgrounds<Box className="bg-error-500">
<Text className="text-typography-0">Error message</Text>
</Box>
<Box className="border border-outline-300 bg-background-50">
<Text className="text-success-600">Success!</Text>
</Box>
<Box className="bg-red-500">
<Text className="text-white">Error message</Text>
</Box>
<Box style={{ backgroundColor: '#DC2626' }}>
<Text style={{ color: 'green' }}>Success!</Text>
</Box>
For complete token reference, see references/color-tokens.md.
Avoid inline style props when className can achieve the same result.
<Box className="w-20 h-20 rounded-full bg-background-100" />
<Text className="text-lg font-bold text-typography-900" />
<Box
style={{
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: "rgba(255, 255, 255, 0.1)",
}}
/>
Inline styles are acceptable for:
// Acceptable: dynamic value from hook
<Box style={{ paddingBottom: bottomInset }} />
// Acceptable: animation value
<Animated.View style={{ transform: [{ translateX: animatedValue }] }} />
Use only values from the standard spacing scale. Arbitrary values create maintenance burden.
| 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 |
p-[13px], m-[27px], gap-[15px]p-2.7, m-4.3<Box className="p-4 m-2 gap-3" />
<Box className="px-6 py-4 mt-8" />
<Box className="p-[13px] m-[27px]" />
<Box style={{ padding: 13, margin: 27 }} />
For complete spacing reference, see references/spacing-scale.md.
Use the CSS variables approach with dark: prefix for dark mode support.
<Box className="bg-background-0 dark:bg-background-950" />
<Text className="text-typography-900 dark:text-typography-0" />
const CardView = ({ isDark }: { readonly isDark: boolean }) => (
<Box className={isDark ? "bg-background-950" : "bg-background-0"}>
<Text className={isDark ? "text-typography-0" : "text-typography-900"}>
Content
</Text>
</Box>
);
Use Gluestack's composable sub-component pattern for complex components.
<Button action="primary" size="md">
<ButtonText>Click Me</ButtonText>
<ButtonIcon as={ChevronRightIcon} />
</Button>
<Input variant="outline" size="md">
<InputField placeholder="Enter text" />
<InputSlot>
<InputIcon as={SearchIcon} />
</InputSlot>
</Input>
<Select>
<SelectTrigger>
<SelectInput placeholder="Select option" />
<SelectIcon as={ChevronDownIcon} />
</SelectTrigger>
<SelectPortal>
<SelectBackdrop />
<SelectContent>
<SelectItem label="Option 1" value="1" />
<SelectItem label="Option 2" value="2" />
</SelectContent>
</SelectPortal>
</Select>
// Missing sub-components
<Button>Click Me</Button>
// Text must be wrapped in ButtonText
<Button>
Click Me {/* Will not render correctly */}
</Button>
For components with multiple style variants, use tva (Tailwind Variant Authority).
import { tva } from "@gluestack-ui/nativewind-utils/tva";
const cardStyles = tva({
base: "rounded-lg p-4",
variants: {
variant: {
elevated: "bg-background-0 shadow-hard-2",
outlined: "bg-transparent border border-outline-200",
filled: "bg-background-50",
},
size: {
sm: "p-2",
md: "p-4",
lg: "p-6",
},
},
defaultVariants: {
variant: "elevated",
size: "md",
},
});
const Card = ({ variant, size, className }: CardProps) => (
<Box className={cardStyles({ variant, size, className })} />
);
Allow className override in custom components using string concatenation.
interface BoxCardProps {
readonly className?: string;
readonly children: React.ReactNode;
}
const BoxCard = ({ className, children }: BoxCardProps) => (
<Box className={`rounded-lg bg-background-0 p-4 ${className ?? ""}`}>
{children}
</Box>
);
To validate styling compliance, run:
python3 .claude/skills/gluestack-nativewind/scripts/validate_styling.py [path]
The script detects:
When a design request cannot be satisfied with existing patterns:
For detailed mappings and complete token lists:
references/component-mapping.md - Gluestack equivalents for React Native primitivesreferences/color-tokens.md - Complete semantic color token referencereferences/spacing-scale.md - Allowed spacing values with pixel equivalents