plugins/src/expo/skills/gluestack-nativewind/SKILL.md
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 skillsauth add codyswanngt/lisa gluestack-nativewindInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 equivalentsdocumentation
Onboard a user to the project via its LLM Wiki. Interviews the user about themselves in relation to the project, captures that to project-scoped memory only, then gives a guided tour of what the project is and sample questions they can ask. Use when someone is new to the project or asks to be onboarded. Read-mostly — it does not open PRs or write PII into the wiki.
documentation
Migrate an existing, hand-rolled wiki implementation onto the lisa-wiki kernel — phased and compatibility-first, with a strict no-loss guarantee. Use when adopting lisa-wiki in a repo that already has its own wiki/, ingest skills, docs, or roles. Renaming things into the canonical shape is fine; losing functionality or data is not. Ends by running /doctor.
development
Health-check the LLM Wiki. Reports orphan pages, contradictions, stale claims, broken internal links, missing index/log coverage, structure-manifest violations, and secret/tenant leaks. Use periodically or before hardening a wiki. Read-only — it reports findings, it does not fix them.
testing
Ingest source material into the LLM Wiki. With an argument (URL, file path, or prompt) it ingests that one source; with no argument it runs a full ingest across every enabled non-external-write source. Routes to the right connector, then runs the ordered pipeline (source note → synthesis → index → log → verify → state → commit/PR). Use whenever new knowledge should enter the wiki.