plugins/lisa-expo-copilot/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.
Before applying this skill, check whether the target project has a sealed or closed design system. Common markers include .claude/rules/use-the-design-library.md, .claude/rules/figma-design-system.md, design-system ESLint rules, or a project-owned @/components/atoms barrel paired with design-system docs/configuration.
If a sealed design system is present, this skill does not apply. Defer to the project's design-library rules and skills instead, such as use-the-design-library and add-design-library-item. Do not instruct @/components/ui imports, Gluestack compound trees, raw shade tokens, margins, or className usage above the atom layer when the project seal forbids them.
For open Gluestack UI v3 + NativeWind v4 projects without those seal markers, continue with the guidance below.
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 equivalentsdevelopment
Prepare a machine — a fresh laptop or a throwaway container — to run coding agents, before any repository exists. Detects which of Lisa's supported agents (Claude Code, Codex, Cursor, OpenCode, Antigravity, Copilot) are already installed, asks which credential manager the machine uses (Bitwarden, 1Password, Doppler, Vault, AWS, or none), and installs only what is missing, each by its vendor's own preferred method. Idempotent, headless by default, and emits a Dockerfile for a spin-up/spin-down environment. Run it on a new machine, in a container, or before cloning anything.
tools
Provision and verify a remote execution environment for a host project — Codex Cloud today, other remote surfaces as they are added. Generates a repository-owned setup script that installs the declared toolchain, materializes secrets through lisa-secrets-access, and runs the project's own hook. Provisions by API where one exists, by driving the vendor console where one does not, and by emitting exact config otherwise — then proves the result with the same read-back regardless of which tier did the work. Use before dispatching any work with executionEnv.
tools
Bring a developer's machine in line with the toolchain the project declares. Reports every tool in remoteEnv.tools that is missing, outdated, or unpinned for this platform, and installs the missing ones into ~/.local/bin from the same pinned, checksummed entries the remote surfaces use — but only when asked. Same manifest, same pins, same installers as lisa-setup-remote-env; what differs is consent and that the pin is a floor rather than an equality. Run it on a fresh checkout, after a manifest change, or when a tool fails at the moment of use.
tools
Route one unit of work to a remote execution surface. Reads the executionEnv parameter (local by default, codex-cloud or claude-web today), verifies the environment is provisioned and bound to this repository, submits a thin skill invocation, records the task identifier to .lisa/remote-dispatch.json, and exits without polling. Routing only — the remote runs the identical skill from the identical repository. Composable and inline: other skills invoke it via the Skill tool rather than users calling it directly.