src/skills/mobile-ui-components-tamagui/SKILL.md
Tamagui universal UI - styled(), tokens, themes, optimizing compiler, responsive media queries, animations, Sheet/Dialog components
npx skillsauth add agents-inc/skills mobile-ui-components-tamaguiInstall 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.
Quick Guide: Tamagui provides universal styled components for React Native and web with an optimizing compiler that flattens components to native primitives. Use
styled()with variants for component APIs,$-prefixed tokens for consistent spacing/color, theme nesting for light/dark modes, and thetransitionprop for animations. The compiler extracts static styles to CSS on web and hoists style objects on native -- but only when props are deterministic at build time.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use $-prefixed token values in style props ($4, $color.blue) -- raw pixel values bypass the token system and break theme consistency)
(You MUST keep the transition prop present in JSX when using animations -- conditionally removing it causes expensive hook teardown; pass null to disable instead)
(You MUST add as const to variant definition objects -- without it TypeScript cannot infer variant prop types correctly)
(You MUST use Adapt for responsive Dialog-to-Sheet behavior -- manual Platform.OS branching breaks compiler optimization and misses breakpoint changes)
</critical_requirements>
Auto-detection: Tamagui, tamagui, styled(), createTamagui, createTokens, createTheme, XStack, YStack, ZStack, SizableText, Paragraph, Theme, useTheme, useMedia, $sm, $md, $lg, enterStyle, exitStyle, hoverStyle, pressStyle, transition prop, Sheet, Dialog, Adapt, GetProps, TamaguiProvider, @tamagui/core, @tamagui/config
When to use:
$sm, $gtMd)AdaptWhen NOT to use:
Key patterns covered:
styled() with typed variants (spread, boolean, functional) and GetProps type extractioncreateTokens, $-prefix references, category-to-property mapping)useTheme, dark/light switching)useMedia hooktransition prop, enterStyle/exitStyle, and driver selectionAdapt for responsive overlay behaviorDetailed Resources:
Tamagui solves the universal UI problem: write components once that render optimally on both React Native and web. The key insight is that compile-time analysis can eliminate the runtime cost of a universal abstraction.
Core principles:
createTokens through themes to components via $-prefixed references. Raw values bypass the system.createTamagui. Component code stays identical.Adapt transforms Dialog to Sheet at breakpoints without manual Platform.OS checks.Tamagui v2 is the current stable release. Key changes from v1: animation prop renamed to transition, config v5 with Tailwind-aligned breakpoints, expanded color system with Radix Colors v3, native portals for Sheet/Dialog/Popover, and headless/unstyled component variants.
styled() extends a base component with default styles and type-safe variants. Use GetProps to export the derived prop type.
import { GetProps, styled, View, Text } from "@tamagui/core";
export const Card = styled(View, {
name: "Card",
backgroundColor: "$background",
borderRadius: "$4",
padding: "$4",
borderWidth: 1,
borderColor: "$borderColor",
variants: {
size: {
"...size": (val, { tokens }) => ({
padding: tokens.size[val] ?? val,
}),
},
elevated: {
true: { elevation: "$2" },
},
} as const,
});
export type CardProps = GetProps<typeof Card>;
Why good: name property enables component-specific themes (dark_Card), spread variant ...size maps all size tokens automatically, as const preserves literal types for variant inference, GetProps keeps prop type in sync with the component
// BAD: raw values bypass token system
export const Card = styled(View, {
padding: 16, // raw pixel -- not theme-aware
borderRadius: 8, // should be $4 or a radius token
backgroundColor: "#fff", // hardcoded -- breaks dark mode
});
Why bad: raw values bypass token resolution and theme switching, hardcoded colors break in dark mode, no connection to design system
See examples/core.md for full variant patterns including boolean, functional, and nested styled(styled()).
Tokens define the design scale. Themes override token values within React subtrees. Components reference $-prefixed keys that resolve to the active theme, falling back to tokens.
// Token reference in JSX
<YStack padding="$4" gap="$2" backgroundColor="$background">
<SizableText size="$5" color="$color">
Themed text
</SizableText>
</YStack>
// Theme nesting -- inner Theme applies as "dark_green"
<Theme name="dark">
<Card>
<Theme name="green">
<Card>{/* uses dark_green theme */}</Card>
</Theme>
</Card>
</Theme>
Why good: $background resolves from active theme (light or dark), nested Theme composes name automatically (dark + green = dark_green), missing keys in sub-theme fall back to parent theme then to tokens
See examples/core.md for createTokens, createTheme, theme definition patterns, and useTheme hook usage.
The compiler flattens styled components to native primitives (div on web, View on native) and extracts atomic CSS. This only works when styles are deterministic at build time.
// GOOD: compiler can flatten -- all values are static tokens
<Card size="$4" elevated />
// GOOD: media query props are compiler-optimized
<YStack padding="$2" $gtMd={{ padding: "$4" }} />
// BAD: dynamic ternary prevents flattening
<YStack padding={isLarge ? "$4" : "$2"} />
// BAD: function render prop deoptimizes
<Card render={(props) => <CustomThing {...props} />} />
Key rule: Static token values, spread variants, and media query props are compiler-friendly. JavaScript expressions, ternaries on runtime values, and function render props force runtime evaluation.
See examples/core.md for what the compiler can/cannot optimize and how to structure code for maximum flattening.
Use $-prefixed media query props for responsive styles. The compiler extracts these to CSS @media rules on web, eliminating runtime overhead.
<XStack
flexDirection="column"
padding="$2"
$gtSm={{ flexDirection: "row", padding: "$4" }}
$gtMd={{ gap: "$4" }}
>
<Card flex={1} />
<Card flex={1} />
</XStack>
Why good: mobile-first base styles, media props override progressively, compiler extracts to CSS @media rules on web (zero JS runtime)
See examples/responsive-animations.md for useMedia hook, config breakpoints, and height-based queries.
The transition prop references a named animation from your config. Combine with enterStyle, exitStyle, hoverStyle, and pressStyle for declarative animations.
<Card
transition="bouncy"
enterStyle={{ opacity: 0, scale: 0.9, y: -10 }}
hoverStyle={{ scale: 1.02 }}
pressStyle={{ scale: 0.98 }}
opacity={1}
scale={1}
y={0}
/>
Why good: declarative animation states, driver-agnostic (CSS on web, Reanimated on native), SSR-safe enterStyle
Critical: always keep transition in JSX -- pass null to disable, never conditionally omit the prop (causes expensive hook teardown).
See examples/responsive-animations.md for driver configuration, AnimatePresence, and per-property animateOnly.
Use Adapt to render Dialog as a Sheet on touch devices at smaller breakpoints. This avoids manual Platform.OS branching and responds to viewport changes.
<Dialog modal>
<Dialog.Trigger asChild>
<Button>Open</Button>
</Dialog.Trigger>
<Adapt when="sm" platform="touch">
<Sheet modal dismissOnSnapToBottom>
<Sheet.Frame padding="$4">
<Adapt.Contents />
</Sheet.Frame>
<Sheet.Overlay />
</Sheet>
</Adapt>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Title</Dialog.Title>
<Dialog.Description>Description</Dialog.Description>
<Dialog.Close asChild>
<Button>Close</Button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
Why good: single component tree handles both desktop dialog and mobile sheet, Adapt.Contents injects Dialog.Content into Sheet.Frame, breakpoint-driven not platform-driven
See examples/overlays.md for controlled Sheet, snap points, ScrollView inside Sheet, and Dialog state preservation.
</patterns><decision_framework>
Is this a reusable component with variants or a semantic name?
├─ YES → styled() with name, variants, defaultVariants
└─ NO → Is this one-off layout?
├─ YES → Inline props on XStack/YStack (<YStack padding="$4">)
└─ NO → styled() if you want component themes or compiler naming
Platform target?
├─ Web only → @tamagui/animations-css (smallest bundle, CSS transitions)
├─ Native only → @tamagui/animations-reanimated (worklet-based, spring physics)
├─ Universal → Configure per-platform in createTamagui
│ ├─ Web: CSS or Motion driver
│ └─ Native: Reanimated or React Native driver
└─ Simple transitions? → @tamagui/animations-react-native (no extra dependency)
Need bottom sheet on mobile?
├─ YES → Is there also a desktop version?
│ ├─ YES → Dialog + Adapt + Sheet (single component tree)
│ └─ NO → Sheet standalone
└─ NO → Dialog (portal-based overlay)
</decision_framework>
<red_flags>
High Priority Issues:
padding: 16) instead of tokens (padding: "$4") -- bypasses theme system, breaks consistency across platformstransition prop from JSX -- causes expensive spring hook teardown/setup; pass null to disable insteadas const on variant definition objects -- TypeScript infers string instead of literal union types, losing autocomplete and type safetyPlatform.OS branching for Dialog vs Sheet -- use Adapt instead, which responds to breakpoints and is compiler-optimized"#fff", "#000") in styled components -- breaks dark/light theme switching; use $background, $colorMedium Priority Issues:
name on styled components that need component-level themes -- without name, Tamagui cannot look up component-specific theme variants like dark_Cardstyled(styled()) without .styleable() when wrapping with a functional component -- variant merging breaks silentlytamagui instead of @tamagui/core when you only need the core -- pulls in the entire UI kit unnecessarilyGotchas & Edge Cases:
<Theme name="dark"><Theme name="green"> resolves to dark_green, not just green. The sub-theme must be defined as dark_green in your config.Object.groupBy on useMedia() result fails: The proxied object from useMedia is not iterable -- use direct key access (media.sm) only.Dialog.Sheet does not preserve state when transitioning between Sheet and Portal modes. Lift state above the Dialog if persistence is needed....size) only match top-level token categories -- custom nested token groups require functional variants instead.elevation prop generates both shadow props (iOS) and elevation (Android) on native, but translates to box-shadow on web. Differences in shadow appearance across platforms are expected.styleCompat: 'react-native', flex uses flexBasis: 0 (not auto). Without it, web defaults apply.@tamagui/animations-reanimated (same API, fewer dependencies)."false" is truthy in JavaScript. Use explicit comparison (val === "true") not coercion.</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST use $-prefixed token values in style props ($4, $color.blue) -- raw pixel values bypass the token system and break theme consistency)
(You MUST keep the transition prop present in JSX when using animations -- conditionally removing it causes expensive hook teardown; pass null to disable instead)
(You MUST add as const to variant definition objects -- without it TypeScript cannot infer variant prop types correctly)
(You MUST use Adapt for responsive Dialog-to-Sheet behavior -- manual Platform.OS branching breaks compiler optimization and misses breakpoint changes)
Failure to follow these rules will break theme consistency, cause animation performance issues, lose type safety on variants, and produce non-adaptive overlays.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events