reusable-ui-components
Best practices for building portable, accessible UI components with native-first design patterns.
Install
mkdir -p .claude/skills/reusable-ui-components && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/3898" && unzip -o skill.zip -d .claude/skills/reusable-ui-components && rm skill.zipInstalls to .claude/skills/reusable-ui-components
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Guidelines for creating reusable, portable UI components with native-first design, compound patterns, and accessibilityKey capabilities
- →Implement compound component patterns for complex UI
- →Apply platform-adaptive styling using CSS variables
- →Integrate native iOS design language and SF Symbols
- →Create portable, self-contained UI components
- →Ensure accessibility with keyboard and screen reader support
How it works
The workflow uses Metro resolution to provide platform-specific overrides for components while maintaining a shared core. It relies on CSS variables and design tokens to ensure consistent theming across iOS, Android, and web.
Inputs & outputs
When to use reusable-ui-components
- →Create reusable button component
- →Implement accessible UI primitives
- →Refactor shared component library
About this skill
Creating Reusable UI Components for Expo Router
This guide covers building production-quality, portable UI components inspired by shadcn/ui, Base UI, Radix, and Konsta UI. Components follow iOS San Francisco design guidelines with liquid glass aesthetics and prioritize native primitives with graceful fallbacks.
Philosophy
Core Principles
- Portable & Copy-Paste Ready - Components should be self-contained and easy to copy between projects
- Native-First - Always check for Expo Router primitives before building custom solutions
- iOS Design Language - Use San Francisco style guide as the baseline for all platforms
- Compound Components - Break complex components into composable sub-components
- CSS Variables for Customization - Use design tokens for theming, not hardcoded values
- Accessibility Built-In - Keyboard handling, safe areas, and screen reader support by default
Inspiration Sources
| Library | Learn From |
|---|---|
| shadcn/ui | Component structure, copy-paste architecture |
| Radix UI | Compound component patterns, accessibility primitives |
| Base UI | Headless component APIs, composition patterns |
| Konsta UI | iOS liquid glass aesthetics, platform-adaptive styling |
Component File Structure
src/components/ui/
├── button.tsx # Default (shared) implementation
├── button.ios.tsx # iOS-specific overrides (optional)
├── button.web.tsx # Web-specific overrides (optional)
└── button.android.tsx # Android-specific overrides (optional)
Metro Resolution Priority:
.ios.tsx/.android.tsx/.web.tsx(platform-specific).native.tsx(iOS + Android).tsx(fallback for all platforms)
Design Tokens & CSS Variables
Global Theme Variables
Define customizable design tokens in src/global.css:
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css";
/* Import Apple system colors */
@import "./css/sf.css";
@layer theme {
@theme {
/* Typography Scale */
--font-sans: system-ui;
--font-mono: ui-monospace;
--font-rounded: ui-rounded;
/* Component Tokens */
--component-radius: 12px;
--component-radius-lg: 16px;
--component-radius-full: 9999px;
/* Spacing Scale */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 24px;
/* Animation */
--transition-fast: 150ms;
--transition-normal: 200ms;
--transition-slow: 300ms;
}
}
/* Platform-specific overrides */
@media ios {
:root {
--font-sans: system-ui;
--font-rounded: ui-rounded;
--component-radius: 10px;
}
}
@media android {
:root {
--font-sans: normal;
--font-rounded: normal;
--component-radius: 8px;
}
}
Apple System Colors
Create platform-adaptive colors in src/css/sf.css:
@layer base {
html {
color-scheme: light dark;
}
}
:root {
/* Primary Colors */
--sf-blue: light-dark(rgb(0 122 255), rgb(10 132 255));
--sf-green: light-dark(rgb(52 199 89), rgb(48 209 89));
--sf-red: light-dark(rgb(255 59 48), rgb(255 69 58));
--sf-orange: light-dark(rgb(255 149 0), rgb(255 159 10));
--sf-yellow: light-dark(rgb(255 204 0), rgb(255 214 10));
--sf-purple: light-dark(rgb(175 82 222), rgb(191 90 242));
--sf-pink: light-dark(rgb(255 45 85), rgb(255 55 95));
/* Gray Scale */
--sf-gray: light-dark(rgb(142 142 147), rgb(142 142 147));
--sf-gray-2: light-dark(rgb(174 174 178), rgb(99 99 102));
--sf-gray-3: light-dark(rgb(199 199 204), rgb(72 72 74));
--sf-gray-4: light-dark(rgb(209 209 214), rgb(58 58 60));
--sf-gray-5: light-dark(rgb(229 229 234), rgb(44 44 46));
--sf-gray-6: light-dark(rgb(242 242 247), rgb(28 28 30));
/* Text Colors */
--sf-text: light-dark(rgb(0 0 0), rgb(255 255 255));
--sf-text-2: light-dark(rgb(60 60 67 / 0.6), rgb(235 235 245 / 0.6));
--sf-text-3: light-dark(rgb(60 60 67 / 0.3), rgb(235 235 245 / 0.3));
--sf-text-placeholder: light-dark(rgb(60 60 67 / 0.3), rgb(235 235 245 / 0.3));
/* Background Colors */
--sf-bg: light-dark(rgb(255 255 255), rgb(0 0 0));
--sf-bg-2: light-dark(rgb(242 242 247), rgb(28 28 30));
--sf-grouped-bg: light-dark(rgb(242 242 247), rgb(0 0 0));
--sf-grouped-bg-2: light-dark(rgb(255 255 255), rgb(28 28 30));
/* Border & Fill */
--sf-border: light-dark(rgb(60 60 67 / 0.12), rgb(84 84 88 / 0.65));
--sf-fill: light-dark(rgb(120 120 128 / 0.2), rgb(120 120 128 / 0.32));
/* Link Color */
--sf-link: var(--sf-blue);
}
/* iOS: Use native platform colors */
@media ios {
:root {
--sf-blue: platformColor(systemBlue);
--sf-green: platformColor(systemGreen);
--sf-red: platformColor(systemRed);
--sf-orange: platformColor(systemOrange);
--sf-yellow: platformColor(systemYellow);
--sf-purple: platformColor(systemPurple);
--sf-pink: platformColor(systemPink);
--sf-gray: platformColor(systemGray);
--sf-gray-2: platformColor(systemGray2);
--sf-gray-3: platformColor(systemGray3);
--sf-gray-4: platformColor(systemGray4);
--sf-gray-5: platformColor(systemGray5);
--sf-gray-6: platformColor(systemGray6);
--sf-text: platformColor(label);
--sf-text-2: platformColor(secondaryLabel);
--sf-text-3: platformColor(tertiaryLabel);
--sf-text-placeholder: platformColor(placeholderText);
--sf-bg: platformColor(systemBackground);
--sf-bg-2: platformColor(secondarySystemBackground);
--sf-grouped-bg: platformColor(systemGroupedBackground);
--sf-grouped-bg-2: platformColor(secondarySystemGroupedBackground);
--sf-border: platformColor(separator);
--sf-fill: platformColor(tertiarySystemFill);
--sf-link: platformColor(link);
}
}
/* Register as Tailwind theme colors */
@layer theme {
@theme {
--color-sf-blue: var(--sf-blue);
--color-sf-green: var(--sf-green);
--color-sf-red: var(--sf-red);
--color-sf-orange: var(--sf-orange);
--color-sf-yellow: var(--sf-yellow);
--color-sf-purple: var(--sf-purple);
--color-sf-pink: var(--sf-pink);
--color-sf-gray: var(--sf-gray);
--color-sf-gray-2: var(--sf-gray-2);
--color-sf-gray-3: var(--sf-gray-3);
--color-sf-gray-4: var(--sf-gray-4);
--color-sf-gray-5: var(--sf-gray-5);
--color-sf-gray-6: var(--sf-gray-6);
--color-sf-text: var(--sf-text);
--color-sf-text-2: var(--sf-text-2);
--color-sf-text-3: var(--sf-text-3);
--color-sf-text-placeholder: var(--sf-text-placeholder);
--color-sf-bg: var(--sf-bg);
--color-sf-bg-2: var(--sf-bg-2);
--color-sf-grouped-bg: var(--sf-grouped-bg);
--color-sf-grouped-bg-2: var(--sf-grouped-bg-2);
--color-sf-border: var(--sf-border);
--color-sf-fill: var(--sf-fill);
--color-sf-link: var(--sf-link);
}
}
Accessing CSS Variables in JavaScript
import { useCSSVariable } from "@/tw";
function MyComponent() {
const primaryColor = useCSSVariable("--sf-blue");
const borderColor = useCSSVariable("--sf-border");
return (
<View style={{ borderColor }}>
<Text style={{ color: primaryColor }}>Hello</Text>
</View>
);
}
Compound Component Pattern
Use compound components for complex, multi-element UI. This provides flexibility while maintaining cohesive behavior.
Template Structure
"use client";
import React, { createContext, use } from "react";
import { View, Text, Pressable } from "@/tw";
import { cn } from "@/lib/utils";
import type { ViewProps, TextProps } from "react-native";
// 1. Define Context for shared state
interface ComponentContextValue {
variant: "default" | "outline" | "ghost";
size: "sm" | "md" | "lg";
disabled?: boolean;
}
const ComponentContext = createContext<ComponentContextValue | null>(null);
function useComponentContext() {
const context = use(ComponentContext);
if (!context) {
throw new Error("Component parts must be used within Component.Root");
}
return context;
}
// 2. Root component provides context
interface RootProps extends ViewProps {
variant?: ComponentContextValue["variant"];
size?: ComponentContextValue["size"];
disabled?: boolean;
}
function Root({
variant = "default",
size = "md",
disabled,
children,
className,
...props
}: RootProps) {
return (
<ComponentContext value={{ variant, size, disabled }}>
<View
{...props}
className={cn(
"flex-row items-center",
disabled && "opacity-50",
className
)}
>
{children}
</View>
</ComponentContext>
);
}
// 3. Sub-components consume context
function Label({ className, ...props }: TextProps) {
const { size } = useComponentContext();
return (
<Text
{...props}
className={cn(
"text-sf-text",
size === "sm" && "text-sm",
size === "md" && "text-base",
size === "lg" && "text-lg",
className
)}
/>
);
}
function Icon({ className, ...props }: ViewProps) {
const { size } = useComponentContext();
const sizeClass = {
sm: "w-4 h-4",
md: "w-5 h-5",
lg: "w-6 h-6",
}[size];
return (
<View {...props} className={cn(sizeClass, className)} />
);
}
// 4. Export as compound component
export const Component = {
Root,
Label,
Icon,
};
// 5. Convenience export for simple usage
export function SimpleComponent(props: RootProps & { label: string }) {
const { label, ...rootProps } = props;
return (
<Component.Root {...rootProps}>
<Component.Label>{label}</Component.Label>
</Component.Root>
);
}
Native-First Component Development
Check for Expo Router Primitives First
Before building custom components, check if Expo Router or Expo provides a native primitive:
| Component Need | Check First |
|---|---|
| Navigation Stack | expo-router Stack |
| Tab Navigation | expo-router Tabs |
| Mod |
Content truncated.
When not to use it
- →When building non-UI logic or backend services
- →Projects requiring heavy reliance on non-native web-only libraries
Prerequisites
Limitations
- →Requires adherence to specific file naming conventions for platform resolution
- →Limited to environments supporting Expo Router and Tailwind CSS
How it compares
This method enforces a native-first architecture with platform-specific file extensions, unlike generic cross-platform approaches that often rely on single-file conditional logic.
Compared to similar skills
reusable-ui-components side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| reusable-ui-components (this skill) | 1 | 6mo | Review | Intermediate |
| radix-ui-design-system | 33 | 2mo | Review | Intermediate |
| verify-bulk-action-bar | 0 | 3mo | Review | Intermediate |
| frontend-patterns | 0 | 29d | Review | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by EvanBacon
View all by EvanBacon →You might also like
radix-ui-design-system
sickn33
Build accessible design systems with Radix UI primitives. Headless component customization, theming strategies, and compound component patterns for production-grade UI libraries.
verify-bulk-action-bar
junnv93
BulkActionBar 패턴 SSOT 검증 — count chip aria-live, role=toolbar, Esc clear, indeterminate Radix, focus management, IME guard. 일괄 작업 UI 변경 시 트리거. canonical = components/common/BulkActionBar.tsx (도메인 무관 generic), components/approvals/BulkActionBar.tsx는 approvals 특화 wrapper.
frontend-patterns
forgivesam168
Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
ui-consistency
waldur
Waldur HomePort UI/UX consistency patterns and design system rules. Use when creating or modifying UI components, tables, forms, buttons, dialogs, empty states, loading states, tooltips, or notifications. Enforces disabled button tooltips, empty state CTAs, proper null value display, and accessibili
frontend-ui-engineering
charlieviettq
Build and refine web UI—component structure, responsive layout, design tokens, state, and accessibility. Use for feature UI work beyond WCAG checks alone.
ui-ux-expert-skill
fercracix33
Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.