Back to list
mrmeg

frontend-design

by mrmeg

0🍴 1📅 Jan 22, 2026

SKILL.md


name: frontend-design description: Create UI components following this Expo template's design system. Use when building components, screens, or interfaces for this React Native project.

This skill guides creation of UI components for this Expo React Native template. Follow these patterns for consistent, theme-aware, cross-platform components.

Quick Start

Essential imports for any UI component:

import { View, StyleSheet, Platform } from "react-native";
import { useTheme } from "@/client/hooks/useTheme";
import { spacing } from "@/client/constants/spacing";
import type { Theme } from "@/client/constants/colors";

Theming System

useTheme() Hook

Central access point for all theming:

const { theme, scheme, getShadowStyle, getContrastingColor, withAlpha } = useTheme();

// Access semantic colors
backgroundColor: theme.colors.background
color: theme.colors.foreground
borderColor: theme.colors.border

// Cross-platform shadows
const shadow = getShadowStyle("soft"); // "base" | "soft" | "sharp" | "subtle"

// Color utilities
const textColor = getContrastingColor(bgColor, palette.white, palette.black);
const fadedColor = withAlpha(theme.colors.primary, 0.5);

Semantic Colors (99% of usage)

ColorPurpose
background / foregroundPage background and primary text
card / cardForegroundCard surfaces and text
primary / primaryForegroundBrand color and text on it
secondary / secondaryForegroundSecondary actions
muted / mutedForegroundSubtle backgrounds and placeholder text
destructive / destructiveForegroundError/danger states
success / warningStatus colors
borderBorders and dividers
overlayModal overlays

Palette (Only for Contrast Calculations)

import { palette } from "@/client/constants/colors";

// ONLY use palette for WCAG contrast calculations
const textColor = getContrastingColor(bgColor, palette.white, palette.black);

// NEVER use palette for regular styling - use theme.colors instead

Spacing System

8px-based scale from @/client/constants/spacing:

TokenValueTokenValue
xxs2pxradiusXs2px
xs4pxradiusSm4px
sm8pxradiusMd8px
md16pxradiusLg12px
lg24pxradiusXl16px
xl32pxradiusFull9999px
xxl48px

Icon sizes: iconXs (12), iconSm (16), iconMd (24), iconLg (32), iconXl (48)

Typography

StyledText Components

import { SansSerifText, SansSerifBoldText, SerifText } from "@/client/components/ui/StyledText";

// Semantic shortcuts
import { TitleText, HeadingText, BodyText, CaptionText } from "@/client/components/ui/StyledText";

Font families: Lato_400Regular, Lato_700Bold, Merriweather_400Regular, Merriweather_700Bold

Styling Patterns

StyleSheet Factory Pattern

const createStyles = (theme: Theme) =>
  StyleSheet.create({
    container: {
      backgroundColor: theme.colors.card,
      borderRadius: spacing.radiusMd,
      borderWidth: 1,
      borderColor: theme.colors.border,
      padding: spacing.md,
    },
  });

// In component
const { theme } = useTheme();
const styles = createStyles(theme);

CRITICAL: React Native Web Nested Array Fix

React Native Web crashes with nested style arrays. Use these patterns:

Solution 1: Spread Arrays (for simple components)

function Component({ style: styleOverride, ...props }) {
  return (
    <View
      style={[
        styles.base,
        ...(styleOverride && typeof styleOverride !== "function"
          ? Array.isArray(styleOverride) ? styleOverride : [styleOverride]
          : []),
      ]}
    />
  );
}

Solution 2: Plain Objects with Flatten (REQUIRED for @rn-primitives menu components)

function MenuComponent({ style: styleOverride, ...props }) {
  return (
    <Primitive
      style={{
        ...baseStyle,
        ...(styleOverride && typeof styleOverride !== "function"
          ? StyleSheet.flatten(styleOverride)
          : {}),
      }}
    />
  );
}

Platform-Specific Styling

// Pointer cursor for interactive elements on web
...(Platform.OS === "web" && { cursor: "pointer" as any }),

// Z-index for web overlays
...(Platform.OS === "web" && { zIndex: 50 }),

// Remove focus outline on web
...(Platform.OS === "web" && { outlineStyle: "none" as any }),

Component Patterns

Size Variants

type ComponentSize = "sm" | "md" | "lg";

const SIZE_CONFIGS: Record<ComponentSize, { height: number; fontSize: number }> = {
  sm: { height: 28, fontSize: 13 },
  md: { height: 32, fontSize: 14 },
  lg: { height: 36, fontSize: 15 },
};

Preset/Variant Pattern

type ButtonPreset = "default" | "outline" | "ghost" | "link" | "destructive";

Composition Over Props

// PREFERRED - flexible composition
<Button onPress={handler}>
  <SansSerifBoldText>Click Me</SansSerifBoldText>
</Button>

// Components accept children, not just text props

@rn-primitives Integration

For complex interactive components, use @rn-primitives:

import * as CheckboxPrimitive from "@rn-primitives/checkbox";

function Checkbox({ checked, onCheckedChange, style: styleOverride }) {
  const { theme } = useTheme();

  return (
    <CheckboxPrimitive.Root
      checked={checked}
      onCheckedChange={onCheckedChange}
      style={{
        borderColor: theme.colors.border,
        ...(Platform.OS === "web" && { cursor: "pointer" as any }),
        ...(styleOverride && typeof styleOverride !== "function"
          ? StyleSheet.flatten(styleOverride)
          : {}),
      }}
    >
      <CheckboxPrimitive.Indicator>
        <Icon as={Check} color={theme.colors.primary} />
      </CheckboxPrimitive.Indicator>
    </CheckboxPrimitive.Root>
  );
}

Component Development Checklist

  • Extract style prop separately to avoid nested arrays
  • Use useTheme() hook for all colors
  • Import and use spacing constants (never hardcode)
  • Add cursor: "pointer" for interactive elements on web
  • Handle both light and dark themes
  • Test on web for nested array crashes
  • Run npx tsc to check TypeScript
  • Add to showcase in app/(main)/showcase.tsx
  • Consider @rn-primitives for complex interactive components
  • Include accessibility attributes (accessibilityRole, etc.)

Full Component Example

import { View, Pressable, StyleSheet, Platform, type StyleProp, type ViewStyle } from "react-native";
import { useTheme } from "@/client/hooks/useTheme";
import { spacing } from "@/client/constants/spacing";
import { SansSerifText } from "@/client/components/ui/StyledText";
import type { Theme } from "@/client/constants/colors";

interface MyCardProps {
  title: string;
  description?: string;
  onPress?: () => void;
  style?: StyleProp<ViewStyle>;
}

export function MyCard({ title, description, onPress, style: styleOverride }: MyCardProps) {
  const { theme, getShadowStyle } = useTheme();
  const styles = createStyles(theme);
  const shadowStyle = StyleSheet.flatten(getShadowStyle("soft"));

  return (
    <Pressable
      onPress={onPress}
      accessibilityRole="button"
      style={({ pressed }) => [
        styles.container,
        shadowStyle,
        pressed && styles.pressed,
        ...(styleOverride && typeof styleOverride !== "function"
          ? Array.isArray(styleOverride) ? styleOverride : [styleOverride]
          : []),
      ]}
    >
      <SansSerifText style={styles.title}>{title}</SansSerifText>
      {description && (
        <SansSerifText style={styles.description}>{description}</SansSerifText>
      )}
    </Pressable>
  );
}

const createStyles = (theme: Theme) =>
  StyleSheet.create({
    container: {
      backgroundColor: theme.colors.card,
      borderRadius: spacing.radiusMd,
      borderWidth: 1,
      borderColor: theme.colors.border,
      padding: spacing.md,
      ...(Platform.OS === "web" && { cursor: "pointer" as any }),
    },
    pressed: {
      opacity: 0.8,
    },
    title: {
      fontSize: 16,
      fontWeight: "600",
      color: theme.colors.cardForeground,
      marginBottom: spacing.xs,
    },
    description: {
      fontSize: 14,
      color: theme.colors.mutedForeground,
    },
  });

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon