RE

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.zip

Installs 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 accessibility
119 charsno explicit “when” trigger
Intermediate

Key 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

You give it
Component design requirements and platform-specific styling tokens
You get back
Portable, platform-adaptive React component

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

  1. Portable & Copy-Paste Ready - Components should be self-contained and easy to copy between projects
  2. Native-First - Always check for Expo Router primitives before building custom solutions
  3. iOS Design Language - Use San Francisco style guide as the baseline for all platforms
  4. Compound Components - Break complex components into composable sub-components
  5. CSS Variables for Customization - Use design tokens for theming, not hardcoded values
  6. Accessibility Built-In - Keyboard handling, safe areas, and screen reader support by default

Inspiration Sources

LibraryLearn From
shadcn/uiComponent structure, copy-paste architecture
Radix UICompound component patterns, accessibility primitives
Base UIHeadless component APIs, composition patterns
Konsta UIiOS 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:

  1. .ios.tsx / .android.tsx / .web.tsx (platform-specific)
  2. .native.tsx (iOS + Android)
  3. .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 NeedCheck First
Navigation Stackexpo-router Stack
Tab Navigationexpo-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

Expo RouterTailwind CSSTypeScript

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.

SkillInstallsUpdatedSafetyDifficulty
reusable-ui-components (this skill)16moReviewIntermediate
radix-ui-design-system332moReviewIntermediate
verify-bulk-action-bar03moReviewIntermediate
frontend-patterns029dReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

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.

33130

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.

00

frontend-patterns

forgivesam168

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.

00

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

00

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.

00

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.

91244

Search skills

Search the agent skills registry