Skip to main content
EVOKORE// BROWSE
>

./browse/prompts

15 NODES
🤖system prompt•7 months ago

api-design-principles

Master REST and GraphQL API design principles to build intuitive,

coding
⭐1
# API Design Principles Master REST and GraphQL API design principles to build intuitive, scalable, and maintainable APIs that delight developers and stand the test of time. ## When to Use This Skill - Designing new REST or GraphQL APIs - Refactoring existing APIs for better usability - Establishing API design standards for your team - Reviewing API specifications before implementation - Migrating between API paradigms (REST to GraphQL, etc.) - Creating developer-friendly API documentation - Optimizing APIs for specific use cases (mobile, third-party integrations) ## Core Concepts ### 1. RESTful Design Principles **Resource-Oriented Architecture** - Resources are nouns (users, orders, products), not verbs - Use HTTP methods for actions (GET, POST, PUT, PATCH, DELETE) - URLs represent resource hierarchies - Consistent naming conventions **HTTP Methods Semantics:** - `GET`: Retrieve resources (idempotent, safe) - `POST`: Create new resources - `PUT`: Replace entire resource (idempotent) - `PATCH`: Partial resource updates - `DELETE`: Remove resources (idempotent) ### 2. GraphQL Design Principles **Schema-First Development** - Types define your domain model - Queries for reading data - Mutations for modifying data - Subscriptions for real-time updates **Query Structure:** - Clients request exactly what they need - Single endpoint, multiple operations - Strongly typed schema - Introspection built-in ### 3. API Versioning Strategies **URL Versioning:** ``` /api/v1/users /api/v2/users ``` **Header Versioning:** ``` Accept: application/vnd.api+json; version=1 ``` **Query Parameter Versioning:** ``` /api/users?version=1 ``` ## REST API Design Patterns ### Pattern 1: Resource Collection Design ```python # Good: Resource-oriented endpoints GET /api/users # List users (with pagination) POST /api/users # Create user GET /api/users/{id} # Get specific user PUT /api/users/{id} # Replace user PATCH /api/users/{id} # Update user fields DELETE /api/users/{id} # Delete user # Nested resources GET /api/users/{id}/orders # Get user's orders POST /api/users/{id}/orders # Create order for user # Bad: Action-oriented endpoints (avoid) POST /api/createUser POST /api/getUserById POST /api/deleteUser ``` ### Pattern 2: Pagination and Filtering ```python from typing import List, Optional from pydantic import BaseModel, Field class PaginationParams(BaseModel): page: int = Field(1, ge=1, description="Page number") page_size: int = Field(20, ge=1, le=100, description="Items per page") class FilterParams(BaseModel): status: Optional[str] = None created_after: Optional[str] = None search: Optional[str] = None class PaginatedResponse(BaseModel): items: List[dict] total: int page: int page_size: int pages: int @property def has_next(self) -> bool: return self.page < self.pages @property def has_prev(self) -> bool: return self.page > 1 # FastAPI endpoint example from fastapi import FastAPI, Query, Depends app = FastAPI() @app.get("/api/users", response_model=PaginatedResponse) async def list_users( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), status: Optional[str] = Query(None), search: Optional[str] = Query(None) ): # Apply filters query = build_query(status=status, search=search) # Count total total = await count_users(query) # Fetch page offset = (page - 1) * page_size users = await fetch_users(query, limit=page_size, offset=offset) return PaginatedResponse( items=users, total=total, page=page, page_size=page_size, pages=(total + page_size - 1) // page_size ) ``` ### Pattern 3: Error Handling and Status Codes ```python from fastapi import HTTPException, status from pydantic import BaseModel class ErrorResponse(BaseModel): error: str message: str details: Optional[dict] = None timestamp: str path: str class ValidationErrorDetail(BaseModel): field: str message: str value: Any # Consistent error responses STATUS_CODES = { "success": 200, "created": 201, "no_content": 204, "bad_request": 400, "unauthorized": 401, "forbidden": 403, "not_found": 404, "conflict": 409, "unprocessable": 422, "internal_error": 500 } def raise_not_found(resource: str, id: str): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": "NotFound", "message": f"{resource} not found", "details": {"id": id} } ) def raise_validation_error(errors: List[ValidationErrorDetail]): raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail={ "error": "ValidationError", "message": "Request validation failed", "details": {"errors": [e.dict() for e in errors]} } ) # Example usage @app.get("/api/users/{user_id}") async def get_user(user_id: str): user = await fetch_user(user_id) if not user: raise_not_found("User", user_id) return user ``` ### Pattern 4: HATEOAS (Hypermedia as the Engine of Application State) ```python class UserResponse(BaseModel): id: str name: str email: str _links: dict @classmethod def from_user(cls, user: User, base_url: str): return cls( id=user.id, name=user.name, email=user.email, _links={ "self": {"href": f"{base_url}/api/users/{user.id}"}, "orders": {"href": f"{base_url}/api/users/{user.id}/orders"}, "update": { "href": f"{base_url}/api/users/{user.id}", "method": "PATCH" }, "delete": { "href": f"{base_url}/api/users/{user.id}", "method": "DELETE" } } ) ``` ## GraphQL Design Patterns ### Pattern 1: Schema Design ```graphql # schema.graphql # Clear type definitions type User { id: ID! email: String! name: String! createdAt: DateTime! # Relationships orders(first: Int = 20, after: String, status: OrderStatus): OrderConnection! profile: UserProfile } type Order { id: ID! status: OrderStatus! total: Money! items: [OrderItem!]! createdAt: DateTime! # Back-reference user: User! } # Pagination pattern (Relay-style) type OrderConnection { edges: [OrderEdge!]! pageInfo: PageInfo! totalCount: Int! } type OrderEdge { node: Order! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } # Enums for type safety enum OrderStatus { PENDING CONFIRMED SHIPPED DELIVERED CANCELLED } # Custom scalars scalar DateTime scalar Money # Query root type Query { user(id: ID!): User users(first: Int = 20, after: String, search: String): UserConnection! order(id: ID!): Order } # Mutation root type Mutation { createUser(input: CreateUserInput!): CreateUserPayload! updateUser(input: UpdateUserInput!): UpdateUserPayload! deleteUser(id: ID!): DeleteUserPayload! createOrder(input: CreateOrderInput!): CreateOrderPayload! } # Input types for mutations input CreateUserInput { email: String! name: String! password: String! } # Payload types for mutations type CreateUserPayload { user: User errors: [Error!] } type Error { field: String message: String! } ``` ### Pattern 2: Resolver Design ```python from typing import Optional, List from ariadne import QueryType, MutationType, ObjectType from dataclasses import dataclass query = QueryType() mutation = MutationType() user_type = ObjectType("User") @query.field("user") async def resolve_user(obj, info, id: str) -> Optional[dict]: """Resolve single user by ID.""" return await fetch_user_by_id(id) @query.field("users") async def resolve_users( obj, info, first: int = 20, after: Optional[str] = None, search: Optional[str] = None ) -> dict: """Resolve paginated user list.""" # Decode cursor offset = decode_cursor(after) if after else 0 # Fetch users users = await fetch_users( limit=first + 1, # Fetch one extra to check hasNextPage offset=offset, search=search ) # Pagination has_next = len(users) > first if has_next: users = users[:first] edges = [ { "node": user, "cursor": encode_cursor(offset + i) } for i, user in enumerate(users) ] return { "edges": edges, "pageInfo": { "hasNextPage": has_next, "hasPreviousPage": offset > 0, "startCursor": edges[0]["cursor"] if edges else None, "endCursor": edges[-1]["cursor"] if edges else None }, "totalCount": await count_users(search=search) } @user_type.field("orders") async def resolve_user_orders(user: dict, info, first: int = 20) -> dict: """Resolve user's orders (N+1 prevention with DataLoader).""" # Use DataLoader to batch requests loader = info.context["loaders"]["orders_by_user"] orders = await loader.load(user["id"]) return paginate_orders(orders, first) @mutation.field("createUser") async def resolve_create_user(obj, info, input: dict) -> dict: """Create new user.""" try: # Validate input validate_user_input(input) # Create user user = await create_user( email=input["email"], name=input["name"], password=hash_password(input["password"]) ) return { "user": user, "errors": [] } except ValidationError as e: return { "user": None, "errors": [{"field": e.field, "message": e.message}] } ``` ### Pattern 3: DataLoader (N+1 Problem Prevention) ```python from aiodataloader import DataLoader from typing import List, Optional class UserLoader(DataLoader): """Batch load users by ID.""" async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]: """Load multiple users in single query.""" users = await fetch_users_by_ids(user_ids) # Map results back to input order user_map = {user["id"]: user for user in users} return [user_map.get(user_id) for user_id in user_ids] class OrdersByUserLoader(DataLoader): """Batch load orders by user ID.""" async def batch_load_fn(self, user_ids: List[str]) -> List[List[dict]]: """Load orders for multiple users in single query.""" orders = await fetch_orders_by_user_ids(user_ids) # Group orders by user_id orders_by_user = {} for order in orders: user_id = order["user_id"] if user_id not in orders_by_user: orders_by_user[user_id] = [] orders_by_user[user_id].append(order) # Return in input order return [orders_by_user.get(user_id, []) for user_id in user_ids] # Context setup def create_context(): return { "loaders": { "user": UserLoader(), "orders_by_user": OrdersByUserLoader() } } ``` ## Best Practices ### REST APIs 1. **Consistent Naming**: Use plural nouns for collections (`/users`, not `/user`) 2. **Stateless**: Each request contains all necessary information 3. **Use HTTP Status Codes Correctly**: 2xx success, 4xx client errors, 5xx server errors 4. **Version Your API**: Plan for breaking changes from day one 5. **Pagination**: Always paginate large collections 6. **Rate Limiting**: Protect your API with rate limits 7. **Documentation**: Use OpenAPI/Swagger for interactive docs ### GraphQL APIs 1. **Schema First**: Design schema before writing resolvers 2. **Avoid N+1**: Use DataLoaders for efficient data fetching 3. **Input Validation**: Validate at schema and resolver levels 4. **Error Handling**: Return structured errors in mutation payloads 5. **Pagination**: Use cursor-based pagination (Relay spec) 6. **Deprecation**: Use `@deprecated` directive for gradual migration 7. **Monitoring**: Track query complexity and execution time ## Common Pitfalls - **Over-fetching/Under-fetching (REST)**: Fixed in GraphQL but requires DataLoaders - **Breaking Changes**: Version APIs or use deprecation strategies - **Inconsistent Error Formats**: Standardize error responses - **Missing Rate Limits**: APIs without limits are vulnerable to abuse - **Poor Documentation**: Undocumented APIs frustrate developers - **Ignoring HTTP Semantics**: POST for idempotent operations breaks expectations - **Tight Coupling**: API structure shouldn't mirror database schema ## Resources - **references/rest-best-practices.md**: Comprehensive REST API design guide - **references/graphql-schema-design.md**: GraphQL schema patterns and anti-patterns - **references/api-versioning-strategies.md**: Versioning approaches and migration paths - **assets/rest-api-template.py**: FastAPI REST API template - **assets/graphql-schema-template.graphql**: Complete GraphQL schema example - **assets/api-design-checklist.md**: Pre-implementation review checklist - **scripts/openapi-generator.py**: Generate OpenAPI specs from code
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

kpi-dashboard-design

Design effective KPI dashboards with metrics selection,

coding
⭐1
# KPI Dashboard Design Comprehensive patterns for designing effective Key Performance Indicator (KPI) dashboards that drive business decisions. ## When to Use This Skill - Designing executive dashboards - Selecting meaningful KPIs - Building real-time monitoring displays - Creating department-specific metrics views - Improving existing dashboard layouts - Establishing metric governance ## Core Concepts ### 1. KPI Framework | Level | Focus | Update Frequency | Audience | | --------------- | ---------------- | ----------------- | ---------- | | **Strategic** | Long-term goals | Monthly/Quarterly | Executives | | **Tactical** | Department goals | Weekly/Monthly | Managers | | **Operational** | Day-to-day | Real-time/Daily | Teams | ### 2. SMART KPIs ``` Specific: Clear definition Measurable: Quantifiable Achievable: Realistic targets Relevant: Aligned to goals Time-bound: Defined period ``` ### 3. Dashboard Hierarchy ``` ├── Executive Summary (1 page) │ ├── 4-6 headline KPIs │ ├── Trend indicators │ └── Key alerts ├── Department Views │ ├── Sales Dashboard │ ├── Marketing Dashboard │ ├── Operations Dashboard │ └── Finance Dashboard └── Detailed Drilldowns ├── Individual metrics └── Root cause analysis ``` ## Common KPIs by Department ### Sales KPIs ```yaml Revenue Metrics: - Monthly Recurring Revenue (MRR) - Annual Recurring Revenue (ARR) - Average Revenue Per User (ARPU) - Revenue Growth Rate Pipeline Metrics: - Sales Pipeline Value - Win Rate - Average Deal Size - Sales Cycle Length Activity Metrics: - Calls/Emails per Rep - Demos Scheduled - Proposals Sent - Close Rate ``` ### Marketing KPIs ```yaml Acquisition: - Cost Per Acquisition (CPA) - Customer Acquisition Cost (CAC) - Lead Volume - Marketing Qualified Leads (MQL) Engagement: - Website Traffic - Conversion Rate - Email Open/Click Rate - Social Engagement ROI: - Marketing ROI - Campaign Performance - Channel Attribution - CAC Payback Period ``` ### Product KPIs ```yaml Usage: - Daily/Monthly Active Users (DAU/MAU) - Session Duration - Feature Adoption Rate - Stickiness (DAU/MAU) Quality: - Net Promoter Score (NPS) - Customer Satisfaction (CSAT) - Bug/Issue Count - Time to Resolution Growth: - User Growth Rate - Activation Rate - Retention Rate - Churn Rate ``` ### Finance KPIs ```yaml Profitability: - Gross Margin - Net Profit Margin - EBITDA - Operating Margin Liquidity: - Current Ratio - Quick Ratio - Cash Flow - Working Capital Efficiency: - Revenue per Employee - Operating Expense Ratio - Days Sales Outstanding - Inventory Turnover ``` ## Dashboard Layout Patterns ### Pattern 1: Executive Summary ``` ┌─────────────────────────────────────────────────────────────┐ │ EXECUTIVE DASHBOARD [Date Range ▼] │ ├─────────────┬─────────────┬─────────────┬─────────────────┤ │ REVENUE │ PROFIT │ CUSTOMERS │ NPS SCORE │ │ $2.4M │ $450K │ 12,450 │ 72 │ │ ▲ 12% │ ▲ 8% │ ▲ 15% │ ▲ 5pts │ ├─────────────┴─────────────┴─────────────┴─────────────────┤ │ │ │ Revenue Trend │ Revenue by Product │ │ ┌───────────────────────┐ │ ┌──────────────────┐ │ │ │ /\ /\ │ │ │ ████████ 45% │ │ │ │ / \ / \ /\ │ │ │ ██████ 32% │ │ │ │ / \/ \ / \ │ │ │ ████ 18% │ │ │ │ / \/ \ │ │ │ ██ 5% │ │ │ └───────────────────────┘ │ └──────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────┤ │ 🔴 Alert: Churn rate exceeded threshold (>5%) │ │ 🟡 Warning: Support ticket volume 20% above average │ └─────────────────────────────────────────────────────────────┘ ``` ### Pattern 2: SaaS Metrics Dashboard ``` ┌─────────────────────────────────────────────────────────────┐ │ SAAS METRICS Jan 2024 [Monthly ▼] │ ├──────────────────────┬──────────────────────────────────────┤ │ ┌────────────────┐ │ MRR GROWTH │ │ │ MRR │ │ ┌────────────────────────────────┐ │ │ │ $125,000 │ │ │ /── │ │ │ │ ▲ 8% │ │ │ /────/ │ │ │ └────────────────┘ │ │ /────/ │ │ │ ┌────────────────┐ │ │ /────/ │ │ │ │ ARR │ │ │ /────/ │ │ │ │ $1,500,000 │ │ └────────────────────────────────┘ │ │ │ ▲ 15% │ │ J F M A M J J A S O N D │ │ └────────────────┘ │ │ ├──────────────────────┼──────────────────────────────────────┤ │ UNIT ECONOMICS │ COHORT RETENTION │ │ │ │ │ CAC: $450 │ Month 1: ████████████████████ 100% │ │ LTV: $2,700 │ Month 3: █████████████████ 85% │ │ LTV/CAC: 6.0x │ Month 6: ████████████████ 80% │ │ │ Month 12: ██████████████ 72% │ │ Payback: 4 months │ │ ├──────────────────────┴──────────────────────────────────────┤ │ CHURN ANALYSIS │ │ ┌──────────┬──────────┬──────────┬──────────────────────┐ │ │ │ Gross │ Net │ Logo │ Expansion │ │ │ │ 4.2% │ 1.8% │ 3.1% │ 2.4% │ │ │ └──────────┴──────────┴──────────┴──────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Pattern 3: Real-time Operations ``` ┌─────────────────────────────────────────────────────────────┐ │ OPERATIONS CENTER Live ● Last: 10:42:15 │ ├────────────────────────────┬────────────────────────────────┤ │ SYSTEM HEALTH │ SERVICE STATUS │ │ ┌──────────────────────┐ │ │ │ │ CPU MEM DISK │ │ ● API Gateway Healthy │ │ │ 45% 72% 58% │ │ ● User Service Healthy │ │ │ ███ ████ ███ │ │ ● Payment Service Degraded │ │ │ ███ ████ ███ │ │ ● Database Healthy │ │ │ ███ ████ ███ │ │ ● Cache Healthy │ │ └──────────────────────┘ │ │ ├────────────────────────────┼────────────────────────────────┤ │ REQUEST THROUGHPUT │ ERROR RATE │ │ ┌──────────────────────┐ │ ┌──────────────────────────┐ │ │ │ ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅ │ │ │ ▁▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │ │ │ └──────────────────────┘ │ └──────────────────────────┘ │ │ Current: 12,450 req/s │ Current: 0.02% │ │ Peak: 18,200 req/s │ Threshold: 1.0% │ ├────────────────────────────┴────────────────────────────────┤ │ RECENT ALERTS │ │ 10:40 🟡 High latency on payment-service (p99 > 500ms) │ │ 10:35 🟢 Resolved: Database connection pool recovered │ │ 10:22 🔴 Payment service circuit breaker tripped │ └─────────────────────────────────────────────────────────────┘ ``` ## Implementation Patterns ### SQL for KPI Calculations ```sql -- Monthly Recurring Revenue (MRR) WITH mrr_calculation AS ( SELECT DATE_TRUNC('month', billing_date) AS month, SUM( CASE subscription_interval WHEN 'monthly' THEN amount WHEN 'yearly' THEN amount / 12 WHEN 'quarterly' THEN amount / 3 END ) AS mrr FROM subscriptions WHERE status = 'active' GROUP BY DATE_TRUNC('month', billing_date) ) SELECT month, mrr, LAG(mrr) OVER (ORDER BY month) AS prev_mrr, (mrr - LAG(mrr) OVER (ORDER BY month)) / LAG(mrr) OVER (ORDER BY month) * 100 AS growth_pct FROM mrr_calculation; -- Cohort Retention WITH cohorts AS ( SELECT user_id, DATE_TRUNC('month', created_at) AS cohort_month FROM users ), activity AS ( SELECT user_id, DATE_TRUNC('month', event_date) AS activity_month FROM user_events WHERE event_type = 'active_session' ) SELECT c.cohort_month, EXTRACT(MONTH FROM age(a.activity_month, c.cohort_month)) AS months_since_signup, COUNT(DISTINCT a.user_id) AS active_users, COUNT(DISTINCT a.user_id)::FLOAT / COUNT(DISTINCT c.user_id) * 100 AS retention_rate FROM cohorts c LEFT JOIN activity a ON c.user_id = a.user_id AND a.activity_month >= c.cohort_month GROUP BY c.cohort_month, EXTRACT(MONTH FROM age(a.activity_month, c.cohort_month)) ORDER BY c.cohort_month, months_since_signup; -- Customer Acquisition Cost (CAC) SELECT DATE_TRUNC('month', acquired_date) AS month, SUM(marketing_spend) / NULLIF(COUNT(new_customers), 0) AS cac, SUM(marketing_spend) AS total_spend, COUNT(new_customers) AS customers_acquired FROM ( SELECT DATE_TRUNC('month', u.created_at) AS acquired_date, u.id AS new_customers, m.spend AS marketing_spend FROM users u JOIN marketing_spend m ON DATE_TRUNC('month', u.created_at) = m.month WHERE u.source = 'marketing' ) acquisition GROUP BY DATE_TRUNC('month', acquired_date); ``` ### Python Dashboard Code (Streamlit) ```python import streamlit as st import pandas as pd import plotly.express as px import plotly.graph_objects as go st.set_page_config(page_title="KPI Dashboard", layout="wide") # Header with date filter col1, col2 = st.columns([3, 1]) with col1: st.title("Executive Dashboard") with col2: date_range = st.selectbox( "Period", ["Last 7 Days", "Last 30 Days", "Last Quarter", "YTD"] ) # KPI Cards def metric_card(label, value, delta, prefix="", suffix=""): delta_color = "green" if delta >= 0 else "red" delta_arrow = "▲" if delta >= 0 else "▼" st.metric( label=label, value=f"{prefix}{value:,.0f}{suffix}", delta=f"{delta_arrow} {abs(delta):.1f}%" ) col1, col2, col3, col4 = st.columns(4) with col1: metric_card("Revenue", 2400000, 12.5, prefix="$") with col2: metric_card("Customers", 12450, 15.2) with col3: metric_card("NPS Score", 72, 5.0) with col4: metric_card("Churn Rate", 4.2, -0.8, suffix="%") # Charts col1, col2 = st.columns(2) with col1: st.subheader("Revenue Trend") revenue_data = pd.DataFrame({ 'Month': pd.date_range('2024-01-01', periods=12, freq='M'), 'Revenue': [180000, 195000, 210000, 225000, 240000, 255000, 270000, 285000, 300000, 315000, 330000, 345000] }) fig = px.line(revenue_data, x='Month', y='Revenue', line_shape='spline', markers=True) fig.update_layout(height=300) st.plotly_chart(fig, use_container_width=True) with col2: st.subheader("Revenue by Product") product_data = pd.DataFrame({ 'Product': ['Enterprise', 'Professional', 'Starter', 'Other'], 'Revenue': [45, 32, 18, 5] }) fig = px.pie(product_data, values='Revenue', names='Product', hole=0.4) fig.update_layout(height=300) st.plotly_chart(fig, use_container_width=True) # Cohort Heatmap st.subheader("Cohort Retention") cohort_data = pd.DataFrame({ 'Cohort': ['Jan', 'Feb', 'Mar', 'Apr', 'May'], 'M0': [100, 100, 100, 100, 100], 'M1': [85, 87, 84, 86, 88], 'M2': [78, 80, 76, 79, None], 'M3': [72, 74, 70, None, None], 'M4': [68, 70, None, None, None], }) fig = go.Figure(data=go.Heatmap( z=cohort_data.iloc[:, 1:].values, x=['M0', 'M1', 'M2', 'M3', 'M4'], y=cohort_data['Cohort'], colorscale='Blues', text=cohort_data.iloc[:, 1:].values, texttemplate='%{text}%', textfont={"size": 12}, )) fig.update_layout(height=250) st.plotly_chart(fig, use_container_width=True) # Alerts Section st.subheader("Alerts") alerts = [ {"level": "error", "message": "Churn rate exceeded threshold (>5%)"}, {"level": "warning", "message": "Support ticket volume 20% above average"}, ] for alert in alerts: if alert["level"] == "error": st.error(f"🔴 {alert['message']}") elif alert["level"] == "warning": st.warning(f"🟡 {alert['message']}") ``` ## Best Practices ### Do's - **Limit to 5-7 KPIs** - Focus on what matters - **Show context** - Comparisons, trends, targets - **Use consistent colors** - Red=bad, green=good - **Enable drilldown** - From summary to detail - **Update appropriately** - Match metric frequency ### Don'ts - **Don't show vanity metrics** - Focus on actionable data - **Don't overcrowd** - White space aids comprehension - **Don't use 3D charts** - They distort perception - **Don't hide methodology** - Document calculations - **Don't ignore mobile** - Ensure responsive design ## Resources - [Stephen Few's Dashboard Design](https://www.perceptualedge.com/articles/visual_business_intelligence/rules_for_using_color.pdf) - [Edward Tufte's Principles](https://www.edwardtufte.com/tufte/) - [Google Data Studio Gallery](https://datastudio.google.com/gallery)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

screen-reader-testing

Test web applications with screen readers including VoiceOver,

coding
⭐1
# Screen Reader Testing Practical guide to testing web applications with screen readers for comprehensive accessibility validation. ## When to Use This Skill - Validating screen reader compatibility - Testing ARIA implementations - Debugging assistive technology issues - Verifying form accessibility - Testing dynamic content announcements - Ensuring navigation accessibility ## Core Concepts ### 1. Major Screen Readers | Screen Reader | Platform | Browser | Usage | | ------------- | --------- | -------------- | ----- | | **VoiceOver** | macOS/iOS | Safari | ~15% | | **NVDA** | Windows | Firefox/Chrome | ~31% | | **JAWS** | Windows | Chrome/IE | ~40% | | **TalkBack** | Android | Chrome | ~10% | | **Narrator** | Windows | Edge | ~4% | ### 2. Testing Priority ``` Minimum Coverage: 1. NVDA + Firefox (Windows) 2. VoiceOver + Safari (macOS) 3. VoiceOver + Safari (iOS) Comprehensive Coverage: + JAWS + Chrome (Windows) + TalkBack + Chrome (Android) + Narrator + Edge (Windows) ``` ### 3. Screen Reader Modes | Mode | Purpose | When Used | | ------------------ | ---------------------- | ----------------- | | **Browse/Virtual** | Read content | Default reading | | **Focus/Forms** | Interact with controls | Filling forms | | **Application** | Custom widgets | ARIA applications | ## VoiceOver (macOS) ### Setup ``` Enable: System Preferences → Accessibility → VoiceOver Toggle: Cmd + F5 Quick Toggle: Triple-press Touch ID ``` ### Essential Commands ``` Navigation: VO = Ctrl + Option (VoiceOver modifier) VO + Right Arrow Next element VO + Left Arrow Previous element VO + Shift + Down Enter group VO + Shift + Up Exit group Reading: VO + A Read all from cursor Ctrl Stop speaking VO + B Read current paragraph Interaction: VO + Space Activate element VO + Shift + M Open menu Tab Next focusable element Shift + Tab Previous focusable element Rotor (VO + U): Navigate by: Headings, Links, Forms, Landmarks Left/Right Arrow Change rotor category Up/Down Arrow Navigate within category Enter Go to item Web Specific: VO + Cmd + H Next heading VO + Cmd + J Next form control VO + Cmd + L Next link VO + Cmd + T Next table ``` ### Testing Checklist ```markdown ## VoiceOver Testing Checklist ### Page Load - [ ] Page title announced - [ ] Main landmark found - [ ] Skip link works ### Navigation - [ ] All headings discoverable via rotor - [ ] Heading levels logical (H1 → H2 → H3) - [ ] Landmarks properly labeled - [ ] Skip links functional ### Links & Buttons - [ ] Link purpose clear - [ ] Button actions described - [ ] New window/tab announced ### Forms - [ ] All labels read with inputs - [ ] Required fields announced - [ ] Error messages read - [ ] Instructions available - [ ] Focus moves to errors ### Dynamic Content - [ ] Alerts announced immediately - [ ] Loading states communicated - [ ] Content updates announced - [ ] Modals trap focus correctly ### Tables - [ ] Headers associated with cells - [ ] Table navigation works - [ ] Complex tables have captions ``` ### Common Issues & Fixes ```html <!-- Issue: Button not announcing purpose --> <button><svg>...</svg></button> <!-- Fix --> <button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button> <!-- Issue: Dynamic content not announced --> <div id="results">New results loaded</div> <!-- Fix --> <div id="results" role="status" aria-live="polite">New results loaded</div> <!-- Issue: Form error not read --> <input type="email" /> <span class="error">Invalid email</span> <!-- Fix --> <input type="email" aria-invalid="true" aria-describedby="email-error" /> <span id="email-error" role="alert">Invalid email</span> ``` ## NVDA (Windows) ### Setup ``` Download: nvaccess.org Start: Ctrl + Alt + N Stop: Insert + Q ``` ### Essential Commands ``` Navigation: Insert = NVDA modifier Down Arrow Next line Up Arrow Previous line Tab Next focusable Shift + Tab Previous focusable Reading: NVDA + Down Arrow Say all Ctrl Stop speech NVDA + Up Arrow Current line Headings: H Next heading Shift + H Previous heading 1-6 Heading level 1-6 Forms: F Next form field B Next button E Next edit field X Next checkbox C Next combo box Links: K Next link U Next unvisited link V Next visited link Landmarks: D Next landmark Shift + D Previous landmark Tables: T Next table Ctrl + Alt + Arrows Navigate cells Elements List (NVDA + F7): Shows all links, headings, form fields, landmarks ``` ### Browse vs Focus Mode ``` NVDA automatically switches modes: - Browse Mode: Arrow keys navigate content - Focus Mode: Arrow keys control interactive elements Manual switch: NVDA + Space Watch for: - "Browse mode" announcement when navigating - "Focus mode" when entering form fields - Application role forces forms mode ``` ### Testing Script ```markdown ## NVDA Test Script ### Initial Load 1. Navigate to page 2. Let page finish loading 3. Press Insert + Down to read all 4. Note: Page title, main content identified? ### Landmark Navigation 1. Press D repeatedly 2. Check: All main areas reachable? 3. Check: Landmarks properly labeled? ### Heading Navigation 1. Press Insert + F7 → Headings 2. Check: Logical heading structure? 3. Press H to navigate headings 4. Check: All sections discoverable? ### Form Testing 1. Press F to find first form field 2. Check: Label read? 3. Fill in invalid data 4. Submit form 5. Check: Errors announced? 6. Check: Focus moved to error? ### Interactive Elements 1. Tab through all interactive elements 2. Check: Each announces role and state 3. Activate buttons with Enter/Space 4. Check: Result announced? ### Dynamic Content 1. Trigger content update 2. Check: Change announced? 3. Open modal 4. Check: Focus trapped? 5. Close modal 6. Check: Focus returns? ``` ## JAWS (Windows) ### Essential Commands ``` Start: Desktop shortcut or Ctrl + Alt + J Virtual Cursor: Auto-enabled in browsers Navigation: Arrow keys Navigate content Tab Next focusable Insert + Down Read all Ctrl Stop speech Quick Keys: H Next heading T Next table F Next form field B Next button G Next graphic L Next list ; Next landmark Forms Mode: Enter Enter forms mode Numpad + Exit forms mode F5 List form fields Lists: Insert + F7 Link list Insert + F6 Heading list Insert + F5 Form field list Tables: Ctrl + Alt + Arrows Table navigation ``` ## TalkBack (Android) ### Setup ``` Enable: Settings → Accessibility → TalkBack Toggle: Hold both volume buttons 3 seconds ``` ### Gestures ``` Explore: Drag finger across screen Next: Swipe right Previous: Swipe left Activate: Double tap Scroll: Two finger swipe Reading Controls (swipe up then right): - Headings - Links - Controls - Characters - Words - Lines - Paragraphs ``` ## Common Test Scenarios ### 1. Modal Dialog ```html <!-- Accessible modal structure --> <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" aria-describedby="dialog-desc" > <h2 id="dialog-title">Confirm Delete</h2> <p id="dialog-desc">This action cannot be undone.</p> <button>Cancel</button> <button>Delete</button> </div> ``` ```javascript // Focus management function openModal(modal) { // Store last focused element lastFocus = document.activeElement; // Move focus to modal modal.querySelector("h2").focus(); // Trap focus modal.addEventListener("keydown", trapFocus); } function closeModal(modal) { // Return focus lastFocus.focus(); } function trapFocus(e) { if (e.key === "Tab") { const focusable = modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ); const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { last.focus(); e.preventDefault(); } else if (!e.shiftKey && document.activeElement === last) { first.focus(); e.preventDefault(); } } if (e.key === "Escape") { closeModal(modal); } } ``` ### 2. Live Regions ```html <!-- Status messages (polite) --> <div role="status" aria-live="polite" aria-atomic="true"> <!-- Content updates will be announced after current speech --> </div> <!-- Alerts (assertive) --> <div role="alert" aria-live="assertive"> <!-- Content updates interrupt current speech --> </div> <!-- Progress updates --> <div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" aria-label="Upload progress" ></div> <!-- Log (additions only) --> <div role="log" aria-live="polite" aria-relevant="additions"> <!-- New messages announced, removals not --> </div> ``` ### 3. Tab Interface ```html <div role="tablist" aria-label="Product information"> <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1"> Description </button> <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2" tabindex="-1" > Reviews </button> </div> <div role="tabpanel" id="panel-1" aria-labelledby="tab-1"> Product description content... </div> <div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden> Reviews content... </div> ``` ```javascript // Tab keyboard navigation tablist.addEventListener("keydown", (e) => { const tabs = [...tablist.querySelectorAll('[role="tab"]')]; const index = tabs.indexOf(document.activeElement); let newIndex; switch (e.key) { case "ArrowRight": newIndex = (index + 1) % tabs.length; break; case "ArrowLeft": newIndex = (index - 1 + tabs.length) % tabs.length; break; case "Home": newIndex = 0; break; case "End": newIndex = tabs.length - 1; break; default: return; } tabs[newIndex].focus(); activateTab(tabs[newIndex]); e.preventDefault(); }); ``` ## Debugging Tips ```javascript // Log what screen reader sees function logAccessibleName(element) { const computed = window.getComputedStyle(element); console.log({ role: element.getAttribute("role") || element.tagName, name: element.getAttribute("aria-label") || element.getAttribute("aria-labelledby") || element.textContent, state: { expanded: element.getAttribute("aria-expanded"), selected: element.getAttribute("aria-selected"), checked: element.getAttribute("aria-checked"), disabled: element.disabled, }, visible: computed.display !== "none" && computed.visibility !== "hidden", }); } ``` ## Best Practices ### Do's - **Test with actual screen readers** - Not just simulators - **Use semantic HTML first** - ARIA is supplemental - **Test in browse and focus modes** - Different experiences - **Verify focus management** - Especially for SPAs - **Test keyboard only first** - Foundation for SR testing ### Don'ts - **Don't assume one SR is enough** - Test multiple - **Don't ignore mobile** - Growing user base - **Don't test only happy path** - Test error states - **Don't skip dynamic content** - Most common issues - **Don't rely on visual testing** - Different experience ## Resources - [VoiceOver User Guide](https://support.apple.com/guide/voiceover/welcome/mac) - [NVDA User Guide](https://www.nvaccess.org/files/nvda/documentation/userGuide.html) - [JAWS Documentation](https://support.freedomscientific.com/Products/Blindness/JAWS) - [WebAIM Screen Reader Survey](https://webaim.org/projects/screenreadersurvey/)
👍1
👁️1
🤖 Auto-discovered
🤖system prompt•7 months ago

workflow-patterns

Use this skill when implementing tasks according to Conductor's TDD

coding
⭐1
# Workflow Patterns Guide for implementing tasks using Conductor's TDD workflow, managing phase checkpoints, handling git commits, and executing the verification protocol that ensures quality throughout implementation. ## When to Use This Skill - Implementing tasks from a track's plan.md - Following TDD red-green-refactor cycle - Completing phase checkpoints - Managing git commits and notes - Understanding quality assurance gates - Handling verification protocols - Recording progress in plan files ## TDD Task Lifecycle Follow these 11 steps for each task: ### Step 1: Select Next Task Read plan.md and identify the next pending `[ ]` task. Select tasks in order within the current phase. Do not skip ahead to later phases. ### Step 2: Mark as In Progress Update plan.md to mark the task as `[~]`: ```markdown - [~] **Task 2.1**: Implement user validation ``` Commit this status change separately from implementation. ### Step 3: RED - Write Failing Tests Write tests that define the expected behavior before writing implementation: - Create test file if needed - Write test cases covering happy path - Write test cases covering edge cases - Write test cases covering error conditions - Run tests - they should FAIL Example: ```python def test_validate_user_email_valid(): user = User(email="test@example.com") assert user.validate_email() is True def test_validate_user_email_invalid(): user = User(email="invalid") assert user.validate_email() is False ``` ### Step 4: GREEN - Implement Minimum Code Write the minimum code necessary to make tests pass: - Focus on making tests green, not perfection - Avoid premature optimization - Keep implementation simple - Run tests - they should PASS ### Step 5: REFACTOR - Improve Clarity With green tests, improve the code: - Extract common patterns - Improve naming - Remove duplication - Simplify logic - Run tests after each change - they should remain GREEN ### Step 6: Verify Coverage Check test coverage meets the 80% target: ```bash pytest --cov=module --cov-report=term-missing ``` If coverage is below 80%: - Identify uncovered lines - Add tests for missing paths - Re-run coverage check ### Step 7: Document Deviations If implementation deviated from plan or introduced new dependencies: - Update tech-stack.md with new dependencies - Note deviations in plan.md task comments - Update spec.md if requirements changed ### Step 8: Commit Implementation Create a focused commit for the task: ```bash git add -A git commit -m "feat(user): implement email validation - Add validate_email method to User class - Handle empty and malformed emails - Add comprehensive test coverage Task: 2.1 Track: user-auth_20250115" ``` Commit message format: - Type: feat, fix, refactor, test, docs, chore - Scope: affected module or component - Summary: imperative, present tense - Body: bullet points of changes - Footer: task and track references ### Step 9: Attach Git Notes Add rich task summary as git note: ```bash git notes add -m "Task 2.1: Implement user validation Summary: - Added email validation using regex pattern - Handles edge cases: empty, no @, no domain - Coverage: 94% on validation module Files changed: - src/models/user.py (modified) - tests/test_user.py (modified) Decisions: - Used simple regex over email-validator library - Reason: No external dependency for basic validation" ``` ### Step 10: Update Plan with SHA Update plan.md to mark task complete with commit SHA: ```markdown - [x] **Task 2.1**: Implement user validation `abc1234` ``` ### Step 11: Commit Plan Update Commit the plan status update: ```bash git add conductor/tracks/*/plan.md git commit -m "docs: update plan - task 2.1 complete Track: user-auth_20250115" ``` ## Phase Completion Protocol When all tasks in a phase are complete, execute the verification protocol: ### Identify Changed Files List all files modified since the last checkpoint: ```bash git diff --name-only <last-checkpoint-sha>..HEAD ``` ### Ensure Test Coverage For each modified file: 1. Identify corresponding test file 2. Verify tests exist for new/changed code 3. Run coverage for modified modules 4. Add tests if coverage < 80% ### Run Full Test Suite Execute complete test suite: ```bash pytest -v --tb=short ``` All tests must pass before proceeding. ### Generate Manual Verification Steps Create checklist of manual verifications: ```markdown ## Phase 1 Verification Checklist - [ ] User can register with valid email - [ ] Invalid email shows appropriate error - [ ] Database stores user correctly - [ ] API returns expected response codes ``` ### WAIT for User Approval Present verification checklist to user: ``` Phase 1 complete. Please verify: 1. [ ] Test suite passes (automated) 2. [ ] Coverage meets target (automated) 3. [ ] Manual verification items (requires human) Respond with 'approved' to continue, or note issues. ``` Do NOT proceed without explicit approval. ### Create Checkpoint Commit After approval, create checkpoint commit: ```bash git add -A git commit -m "checkpoint: phase 1 complete - user-auth_20250115 Verified: - All tests passing - Coverage: 87% - Manual verification approved Phase 1 tasks: - [x] Task 1.1: Setup database schema - [x] Task 1.2: Implement user model - [x] Task 1.3: Add validation logic" ``` ### Record Checkpoint SHA Update plan.md checkpoints table: ```markdown ## Checkpoints | Phase | Checkpoint SHA | Date | Status | | ------- | -------------- | ---------- | -------- | | Phase 1 | def5678 | 2025-01-15 | verified | | Phase 2 | | | pending | ``` ## Quality Assurance Gates Before marking any task complete, verify these gates: ### Passing Tests - All existing tests pass - New tests pass - No test regressions ### Coverage >= 80% - New code has 80%+ coverage - Overall project coverage maintained - Critical paths fully covered ### Style Compliance - Code follows style guides - Linting passes - Formatting correct ### Documentation - Public APIs documented - Complex logic explained - README updated if needed ### Type Safety - Type hints present (if applicable) - Type checker passes - No type: ignore without reason ### No Linting Errors - Zero linter errors - Warnings addressed or justified - Static analysis clean ### Mobile Compatibility If applicable: - Responsive design verified - Touch interactions work - Performance acceptable ### Security Audit - No secrets in code - Input validation present - Authentication/authorization correct - Dependencies vulnerability-free ## Git Integration ### Commit Message Format ``` <type>(<scope>): <subject> <body> <footer> ``` Types: - `feat`: New feature - `fix`: Bug fix - `refactor`: Code change without feature/fix - `test`: Adding tests - `docs`: Documentation - `chore`: Maintenance ### Git Notes for Rich Summaries Attach detailed notes to commits: ```bash git notes add -m "<detailed summary>" ``` View notes: ```bash git log --show-notes ``` Benefits: - Preserves context without cluttering commit message - Enables semantic queries across commits - Supports track-based operations ### SHA Recording in plan.md Always record the commit SHA when completing tasks: ```markdown - [x] **Task 1.1**: Setup schema `abc1234` - [x] **Task 1.2**: Add model `def5678` ``` This enables: - Traceability from plan to code - Semantic revert operations - Progress auditing ## Verification Checkpoints ### Why Checkpoints Matter Checkpoints create restore points for semantic reversion: - Revert to end of any phase - Maintain logical code state - Enable safe experimentation ### When to Create Checkpoints Create checkpoint after: - All phase tasks complete - All phase verifications pass - User approval received ### Checkpoint Commit Content Include in checkpoint commit: - All uncommitted changes - Updated plan.md - Updated metadata.json - Any documentation updates ### How to Use Checkpoints For reverting: ```bash # Revert to end of Phase 1 git revert --no-commit <phase-2-commits>... git commit -m "revert: rollback to phase 1 checkpoint" ``` For review: ```bash # See what changed in Phase 2 git diff <phase-1-sha>..<phase-2-sha> ``` ## Handling Deviations During implementation, deviations from the plan may occur. Handle them systematically: ### Types of Deviations **Scope Addition** Discovered requirement not in original spec. - Document in spec.md as new requirement - Add tasks to plan.md - Note addition in task comments **Scope Reduction** Feature deemed unnecessary during implementation. - Mark tasks as `[-]` (skipped) with reason - Update spec.md scope section - Document decision rationale **Technical Deviation** Different implementation approach than planned. - Note deviation in task completion comment - Update tech-stack.md if dependencies changed - Document why original approach was unsuitable **Requirement Change** Understanding of requirement changes during work. - Update spec.md with corrected requirement - Adjust plan.md tasks if needed - Re-verify acceptance criteria ### Deviation Documentation Format When completing a task with deviation: ```markdown - [x] **Task 2.1**: Implement validation `abc1234` - DEVIATION: Used library instead of custom code - Reason: Better edge case handling - Impact: Added email-validator to dependencies ``` ## Error Recovery ### Failed Tests After GREEN If tests fail after reaching GREEN: 1. Do NOT proceed to REFACTOR 2. Identify which test started failing 3. Check if refactoring broke something 4. Revert to last known GREEN state 5. Re-approach the implementation ### Checkpoint Rejection If user rejects a checkpoint: 1. Note rejection reason in plan.md 2. Create tasks to address issues 3. Complete remediation tasks 4. Request checkpoint approval again ### Blocked by Dependency If task cannot proceed: 1. Mark task as `[!]` with blocker description 2. Check if other tasks can proceed 3. Document expected resolution timeline 4. Consider creating dependency resolution track ## TDD Variations by Task Type ### Data Model Tasks ``` RED: Write test for model creation and validation GREEN: Implement model class with fields REFACTOR: Add computed properties, improve types ``` ### API Endpoint Tasks ``` RED: Write test for request/response contract GREEN: Implement endpoint handler REFACTOR: Extract validation, improve error handling ``` ### Integration Tasks ``` RED: Write test for component interaction GREEN: Wire components together REFACTOR: Improve error propagation, add logging ``` ### Refactoring Tasks ``` RED: Add characterization tests for current behavior GREEN: Apply refactoring (tests should stay green) REFACTOR: Clean up any introduced complexity ``` ## Working with Existing Tests When modifying code with existing tests: ### Extend, Don't Replace - Keep existing tests passing - Add new tests for new behavior - Update tests only when requirements change ### Test Migration When refactoring changes test structure: 1. Run existing tests (should pass) 2. Add new tests for refactored code 3. Migrate test cases to new structure 4. Remove old tests only after new tests pass ### Regression Prevention After any change: 1. Run full test suite 2. Check for unexpected failures 3. Investigate any new failures 4. Fix regressions before proceeding ## Checkpoint Verification Details ### Automated Verification Run before requesting approval: ```bash # Test suite pytest -v --tb=short # Coverage pytest --cov=src --cov-report=term-missing # Linting ruff check src/ tests/ # Type checking (if applicable) mypy src/ ``` ### Manual Verification Guidance For manual items, provide specific instructions: ```markdown ## Manual Verification Steps ### User Registration 1. Navigate to /register 2. Enter valid email: test@example.com 3. Enter password meeting requirements 4. Click Submit 5. Verify success message appears 6. Verify user appears in database ### Error Handling 1. Enter invalid email: "notanemail" 2. Verify error message shows 3. Verify form retains other entered data ``` ## Performance Considerations ### Test Suite Performance Keep test suite fast: - Use fixtures to avoid redundant setup - Mock slow external calls - Run subset during development, full suite at checkpoints ### Commit Performance Keep commits atomic: - One logical change per commit - Complete thought, not work-in-progress - Tests should pass after every commit ## Best Practices 1. **Never skip RED**: Always write failing tests first 2. **Small commits**: One logical change per commit 3. **Immediate updates**: Update plan.md right after task completion 4. **Wait for approval**: Never skip checkpoint verification 5. **Rich git notes**: Include context that helps future understanding 6. **Coverage discipline**: Don't accept coverage below target 7. **Quality gates**: Check all gates before marking complete 8. **Sequential phases**: Complete phases in order 9. **Document deviations**: Note any changes from original plan 10. **Clean state**: Each commit should leave code in working state 11. **Fast feedback**: Run relevant tests frequently during development 12. **Clear blockers**: Address blockers promptly, don't work around them
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

e2e-testing-patterns

Master end-to-end testing with Playwright and Cypress to build

coding
⭐1
# E2E Testing Patterns Build reliable, fast, and maintainable end-to-end test suites that provide confidence to ship code quickly and catch regressions before users do. ## When to Use This Skill - Implementing end-to-end test automation - Debugging flaky or unreliable tests - Testing critical user workflows - Setting up CI/CD test pipelines - Testing across multiple browsers - Validating accessibility requirements - Testing responsive designs - Establishing E2E testing standards ## Core Concepts ### 1. E2E Testing Fundamentals **What to Test with E2E:** - Critical user journeys (login, checkout, signup) - Complex interactions (drag-and-drop, multi-step forms) - Cross-browser compatibility - Real API integration - Authentication flows **What NOT to Test with E2E:** - Unit-level logic (use unit tests) - API contracts (use integration tests) - Edge cases (too slow) - Internal implementation details ### 2. Test Philosophy **The Testing Pyramid:** ``` /\ /E2E\ ← Few, focused on critical paths /─────\ /Integr\ ← More, test component interactions /────────\ /Unit Tests\ ← Many, fast, isolated /────────────\ ``` **Best Practices:** - Test user behavior, not implementation - Keep tests independent - Make tests deterministic - Optimize for speed - Use data-testid, not CSS selectors ## Playwright Patterns ### Setup and Configuration ```typescript // playwright.config.ts import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ testDir: "./e2e", timeout: 30000, expect: { timeout: 5000, }, fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [["html"], ["junit", { outputFile: "results.xml" }]], use: { baseURL: "http://localhost:3000", trace: "on-first-retry", screenshot: "only-on-failure", video: "retain-on-failure", }, projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] } }, { name: "firefox", use: { ...devices["Desktop Firefox"] } }, { name: "webkit", use: { ...devices["Desktop Safari"] } }, { name: "mobile", use: { ...devices["iPhone 13"] } }, ], }); ``` ### Pattern 1: Page Object Model ```typescript // pages/LoginPage.ts import { Page, Locator } from "@playwright/test"; export class LoginPage { readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator; constructor(page: Page) { this.page = page; this.emailInput = page.getByLabel("Email"); this.passwordInput = page.getByLabel("Password"); this.loginButton = page.getByRole("button", { name: "Login" }); this.errorMessage = page.getByRole("alert"); } async goto() { await this.page.goto("/login"); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); } async getErrorMessage(): Promise<string> { return (await this.errorMessage.textContent()) ?? ""; } } // Test using Page Object import { test, expect } from "@playwright/test"; import { LoginPage } from "./pages/LoginPage"; test("successful login", async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login("user@example.com", "password123"); await expect(page).toHaveURL("/dashboard"); await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible(); }); test("failed login shows error", async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login("invalid@example.com", "wrong"); const error = await loginPage.getErrorMessage(); expect(error).toContain("Invalid credentials"); }); ``` ### Pattern 2: Fixtures for Test Data ```typescript // fixtures/test-data.ts import { test as base } from "@playwright/test"; type TestData = { testUser: { email: string; password: string; name: string; }; adminUser: { email: string; password: string; }; }; export const test = base.extend<TestData>({ testUser: async ({}, use) => { const user = { email: `test-${Date.now()}@example.com`, password: "Test123!@#", name: "Test User", }; // Setup: Create user in database await createTestUser(user); await use(user); // Teardown: Clean up user await deleteTestUser(user.email); }, adminUser: async ({}, use) => { await use({ email: "admin@example.com", password: process.env.ADMIN_PASSWORD!, }); }, }); // Usage in tests import { test } from "./fixtures/test-data"; test("user can update profile", async ({ page, testUser }) => { await page.goto("/login"); await page.getByLabel("Email").fill(testUser.email); await page.getByLabel("Password").fill(testUser.password); await page.getByRole("button", { name: "Login" }).click(); await page.goto("/profile"); await page.getByLabel("Name").fill("Updated Name"); await page.getByRole("button", { name: "Save" }).click(); await expect(page.getByText("Profile updated")).toBeVisible(); }); ``` ### Pattern 3: Waiting Strategies ```typescript // ❌ Bad: Fixed timeouts await page.waitForTimeout(3000); // Flaky! // ✅ Good: Wait for specific conditions await page.waitForLoadState("networkidle"); await page.waitForURL("/dashboard"); await page.waitForSelector('[data-testid="user-profile"]'); // ✅ Better: Auto-waiting with assertions await expect(page.getByText("Welcome")).toBeVisible(); await expect(page.getByRole("button", { name: "Submit" })).toBeEnabled(); // Wait for API response const responsePromise = page.waitForResponse( (response) => response.url().includes("/api/users") && response.status() === 200, ); await page.getByRole("button", { name: "Load Users" }).click(); const response = await responsePromise; const data = await response.json(); expect(data.users).toHaveLength(10); // Wait for multiple conditions await Promise.all([ page.waitForURL("/success"), page.waitForLoadState("networkidle"), expect(page.getByText("Payment successful")).toBeVisible(), ]); ``` ### Pattern 4: Network Mocking and Interception ```typescript // Mock API responses test("displays error when API fails", async ({ page }) => { await page.route("**/api/users", (route) => { route.fulfill({ status: 500, contentType: "application/json", body: JSON.stringify({ error: "Internal Server Error" }), }); }); await page.goto("/users"); await expect(page.getByText("Failed to load users")).toBeVisible(); }); // Intercept and modify requests test("can modify API request", async ({ page }) => { await page.route("**/api/users", async (route) => { const request = route.request(); const postData = JSON.parse(request.postData() || "{}"); // Modify request postData.role = "admin"; await route.continue({ postData: JSON.stringify(postData), }); }); // Test continues... }); // Mock third-party services test("payment flow with mocked Stripe", async ({ page }) => { await page.route("**/api/stripe/**", (route) => { route.fulfill({ status: 200, body: JSON.stringify({ id: "mock_payment_id", status: "succeeded", }), }); }); // Test payment flow with mocked response }); ``` ## Cypress Patterns ### Setup and Configuration ```typescript // cypress.config.ts import { defineConfig } from "cypress"; export default defineConfig({ e2e: { baseUrl: "http://localhost:3000", viewportWidth: 1280, viewportHeight: 720, video: false, screenshotOnRunFailure: true, defaultCommandTimeout: 10000, requestTimeout: 10000, setupNodeEvents(on, config) { // Implement node event listeners }, }, }); ``` ### Pattern 1: Custom Commands ```typescript // cypress/support/commands.ts declare global { namespace Cypress { interface Chainable { login(email: string, password: string): Chainable<void>; createUser(userData: UserData): Chainable<User>; dataCy(value: string): Chainable<JQuery<HTMLElement>>; } } } Cypress.Commands.add("login", (email: string, password: string) => { cy.visit("/login"); cy.get('[data-testid="email"]').type(email); cy.get('[data-testid="password"]').type(password); cy.get('[data-testid="login-button"]').click(); cy.url().should("include", "/dashboard"); }); Cypress.Commands.add("createUser", (userData: UserData) => { return cy.request("POST", "/api/users", userData).its("body"); }); Cypress.Commands.add("dataCy", (value: string) => { return cy.get(`[data-cy="${value}"]`); }); // Usage cy.login("user@example.com", "password"); cy.dataCy("submit-button").click(); ``` ### Pattern 2: Cypress Intercept ```typescript // Mock API calls cy.intercept("GET", "/api/users", { statusCode: 200, body: [ { id: 1, name: "John" }, { id: 2, name: "Jane" }, ], }).as("getUsers"); cy.visit("/users"); cy.wait("@getUsers"); cy.get('[data-testid="user-list"]').children().should("have.length", 2); // Modify responses cy.intercept("GET", "/api/users", (req) => { req.reply((res) => { // Modify response res.body.users = res.body.users.slice(0, 5); res.send(); }); }); // Simulate slow network cy.intercept("GET", "/api/data", (req) => { req.reply((res) => { res.delay(3000); // 3 second delay res.send(); }); }); ``` ## Advanced Patterns ### Pattern 1: Visual Regression Testing ```typescript // With Playwright import { test, expect } from "@playwright/test"; test("homepage looks correct", async ({ page }) => { await page.goto("/"); await expect(page).toHaveScreenshot("homepage.png", { fullPage: true, maxDiffPixels: 100, }); }); test("button in all states", async ({ page }) => { await page.goto("/components"); const button = page.getByRole("button", { name: "Submit" }); // Default state await expect(button).toHaveScreenshot("button-default.png"); // Hover state await button.hover(); await expect(button).toHaveScreenshot("button-hover.png"); // Disabled state await button.evaluate((el) => el.setAttribute("disabled", "true")); await expect(button).toHaveScreenshot("button-disabled.png"); }); ``` ### Pattern 2: Parallel Testing with Sharding ```typescript // playwright.config.ts export default defineConfig({ projects: [ { name: "shard-1", use: { ...devices["Desktop Chrome"] }, grepInvert: /@slow/, shard: { current: 1, total: 4 }, }, { name: "shard-2", use: { ...devices["Desktop Chrome"] }, shard: { current: 2, total: 4 }, }, // ... more shards ], }); // Run in CI // npx playwright test --shard=1/4 // npx playwright test --shard=2/4 ``` ### Pattern 3: Accessibility Testing ```typescript // Install: npm install @axe-core/playwright import { test, expect } from "@playwright/test"; import AxeBuilder from "@axe-core/playwright"; test("page should not have accessibility violations", async ({ page }) => { await page.goto("/"); const accessibilityScanResults = await new AxeBuilder({ page }) .exclude("#third-party-widget") .analyze(); expect(accessibilityScanResults.violations).toEqual([]); }); test("form is accessible", async ({ page }) => { await page.goto("/signup"); const results = await new AxeBuilder({ page }).include("form").analyze(); expect(results.violations).toEqual([]); }); ``` ## Best Practices 1. **Use Data Attributes**: `data-testid` or `data-cy` for stable selectors 2. **Avoid Brittle Selectors**: Don't rely on CSS classes or DOM structure 3. **Test User Behavior**: Click, type, see - not implementation details 4. **Keep Tests Independent**: Each test should run in isolation 5. **Clean Up Test Data**: Create and destroy test data in each test 6. **Use Page Objects**: Encapsulate page logic 7. **Meaningful Assertions**: Check actual user-visible behavior 8. **Optimize for Speed**: Mock when possible, parallel execution ```typescript // ❌ Bad selectors cy.get(".btn.btn-primary.submit-button").click(); cy.get("div > form > div:nth-child(2) > input").type("text"); // ✅ Good selectors cy.getByRole("button", { name: "Submit" }).click(); cy.getByLabel("Email address").type("user@example.com"); cy.get('[data-testid="email-input"]').type("user@example.com"); ``` ## Common Pitfalls - **Flaky Tests**: Use proper waits, not fixed timeouts - **Slow Tests**: Mock external APIs, use parallel execution - **Over-Testing**: Don't test every edge case with E2E - **Coupled Tests**: Tests should not depend on each other - **Poor Selectors**: Avoid CSS classes and nth-child - **No Cleanup**: Clean up test data after each test - **Testing Implementation**: Test user behavior, not internals ## Debugging Failing Tests ```typescript // Playwright debugging // 1. Run in headed mode npx playwright test --headed // 2. Run in debug mode npx playwright test --debug // 3. Use trace viewer await page.screenshot({ path: 'screenshot.png' }); await page.video()?.saveAs('video.webm'); // 4. Add test.step for better reporting test('checkout flow', async ({ page }) => { await test.step('Add item to cart', async () => { await page.goto('/products'); await page.getByRole('button', { name: 'Add to Cart' }).click(); }); await test.step('Proceed to checkout', async () => { await page.goto('/cart'); await page.getByRole('button', { name: 'Checkout' }).click(); }); }); // 5. Inspect page state await page.pause(); // Pauses execution, opens inspector ``` ## Resources - **references/playwright-best-practices.md**: Playwright-specific patterns - **references/cypress-best-practices.md**: Cypress-specific patterns - **references/flaky-test-debugging.md**: Debugging unreliable tests - **assets/e2e-testing-checklist.md**: What to test with E2E - **assets/selector-strategies.md**: Finding reliable selectors - **scripts/test-analyzer.ts**: Analyze test flakiness and duration
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

react-native-architecture

Build production React Native apps with Expo, navigation, native

coding
⭐1
# React Native Architecture Production-ready patterns for React Native development with Expo, including navigation, state management, native modules, and offline-first architecture. ## When to Use This Skill - Starting a new React Native or Expo project - Implementing complex navigation patterns - Integrating native modules and platform APIs - Building offline-first mobile applications - Optimizing React Native performance - Setting up CI/CD for mobile releases ## Core Concepts ### 1. Project Structure ``` src/ ├── app/ # Expo Router screens │ ├── (auth)/ # Auth group │ ├── (tabs)/ # Tab navigation │ └── _layout.tsx # Root layout ├── components/ │ ├── ui/ # Reusable UI components │ └── features/ # Feature-specific components ├── hooks/ # Custom hooks ├── services/ # API and native services ├── stores/ # State management ├── utils/ # Utilities └── types/ # TypeScript types ``` ### 2. Expo vs Bare React Native | Feature | Expo | Bare RN | | ------------------ | -------------- | -------------- | | Setup complexity | Low | High | | Native modules | EAS Build | Manual linking | | OTA updates | Built-in | Manual setup | | Build service | EAS | Custom CI | | Custom native code | Config plugins | Direct access | ## Quick Start ```bash # Create new Expo project npx create-expo-app@latest my-app -t expo-template-blank-typescript # Install essential dependencies npx expo install expo-router expo-status-bar react-native-safe-area-context npx expo install @react-native-async-storage/async-storage npx expo install expo-secure-store expo-haptics ``` ```typescript // app/_layout.tsx import { Stack } from 'expo-router' import { ThemeProvider } from '@/providers/ThemeProvider' import { QueryProvider } from '@/providers/QueryProvider' export default function RootLayout() { return ( <QueryProvider> <ThemeProvider> <Stack screenOptions={{ headerShown: false }}> <Stack.Screen name="(tabs)" /> <Stack.Screen name="(auth)" /> <Stack.Screen name="modal" options={{ presentation: 'modal' }} /> </Stack> </ThemeProvider> </QueryProvider> ) } ``` ## Patterns ### Pattern 1: Expo Router Navigation ```typescript // app/(tabs)/_layout.tsx import { Tabs } from 'expo-router' import { Home, Search, User, Settings } from 'lucide-react-native' import { useTheme } from '@/hooks/useTheme' export default function TabLayout() { const { colors } = useTheme() return ( <Tabs screenOptions={{ tabBarActiveTintColor: colors.primary, tabBarInactiveTintColor: colors.textMuted, tabBarStyle: { backgroundColor: colors.background }, headerShown: false, }} > <Tabs.Screen name="index" options={{ title: 'Home', tabBarIcon: ({ color, size }) => <Home size={size} color={color} />, }} /> <Tabs.Screen name="search" options={{ title: 'Search', tabBarIcon: ({ color, size }) => <Search size={size} color={color} />, }} /> <Tabs.Screen name="profile" options={{ title: 'Profile', tabBarIcon: ({ color, size }) => <User size={size} color={color} />, }} /> <Tabs.Screen name="settings" options={{ title: 'Settings', tabBarIcon: ({ color, size }) => <Settings size={size} color={color} />, }} /> </Tabs> ) } // app/(tabs)/profile/[id].tsx - Dynamic route import { useLocalSearchParams } from 'expo-router' export default function ProfileScreen() { const { id } = useLocalSearchParams<{ id: string }>() return <UserProfile userId={id} /> } // Navigation from anywhere import { router } from 'expo-router' // Programmatic navigation router.push('/profile/123') router.replace('/login') router.back() // With params router.push({ pathname: '/product/[id]', params: { id: '123', referrer: 'home' }, }) ``` ### Pattern 2: Authentication Flow ```typescript // providers/AuthProvider.tsx import { createContext, useContext, useEffect, useState } from 'react' import { useRouter, useSegments } from 'expo-router' import * as SecureStore from 'expo-secure-store' interface AuthContextType { user: User | null isLoading: boolean signIn: (credentials: Credentials) => Promise<void> signOut: () => Promise<void> } const AuthContext = createContext<AuthContextType | null>(null) export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<User | null>(null) const [isLoading, setIsLoading] = useState(true) const segments = useSegments() const router = useRouter() // Check authentication on mount useEffect(() => { checkAuth() }, []) // Protect routes useEffect(() => { if (isLoading) return const inAuthGroup = segments[0] === '(auth)' if (!user && !inAuthGroup) { router.replace('/login') } else if (user && inAuthGroup) { router.replace('/(tabs)') } }, [user, segments, isLoading]) async function checkAuth() { try { const token = await SecureStore.getItemAsync('authToken') if (token) { const userData = await api.getUser(token) setUser(userData) } } catch (error) { await SecureStore.deleteItemAsync('authToken') } finally { setIsLoading(false) } } async function signIn(credentials: Credentials) { const { token, user } = await api.login(credentials) await SecureStore.setItemAsync('authToken', token) setUser(user) } async function signOut() { await SecureStore.deleteItemAsync('authToken') setUser(null) } if (isLoading) { return <SplashScreen /> } return ( <AuthContext.Provider value={{ user, isLoading, signIn, signOut }}> {children} </AuthContext.Provider> ) } export const useAuth = () => { const context = useContext(AuthContext) if (!context) throw new Error('useAuth must be used within AuthProvider') return context } ``` ### Pattern 3: Offline-First with React Query ```typescript // providers/QueryProvider.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister' import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client' import AsyncStorage from '@react-native-async-storage/async-storage' import NetInfo from '@react-native-community/netinfo' import { onlineManager } from '@tanstack/react-query' // Sync online status onlineManager.setEventListener((setOnline) => { return NetInfo.addEventListener((state) => { setOnline(!!state.isConnected) }) }) const queryClient = new QueryClient({ defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24, // 24 hours staleTime: 1000 * 60 * 5, // 5 minutes retry: 2, networkMode: 'offlineFirst', }, mutations: { networkMode: 'offlineFirst', }, }, }) const asyncStoragePersister = createAsyncStoragePersister({ storage: AsyncStorage, key: 'REACT_QUERY_OFFLINE_CACHE', }) export function QueryProvider({ children }: { children: React.ReactNode }) { return ( <PersistQueryClientProvider client={queryClient} persistOptions={{ persister: asyncStoragePersister }} > {children} </PersistQueryClientProvider> ) } // hooks/useProducts.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' export function useProducts() { return useQuery({ queryKey: ['products'], queryFn: api.getProducts, // Use stale data while revalidating placeholderData: (previousData) => previousData, }) } export function useCreateProduct() { const queryClient = useQueryClient() return useMutation({ mutationFn: api.createProduct, // Optimistic update onMutate: async (newProduct) => { await queryClient.cancelQueries({ queryKey: ['products'] }) const previous = queryClient.getQueryData(['products']) queryClient.setQueryData(['products'], (old: Product[]) => [ ...old, { ...newProduct, id: 'temp-' + Date.now() }, ]) return { previous } }, onError: (err, newProduct, context) => { queryClient.setQueryData(['products'], context?.previous) }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['products'] }) }, }) } ``` ### Pattern 4: Native Module Integration ```typescript // services/haptics.ts import * as Haptics from "expo-haptics"; import { Platform } from "react-native"; export const haptics = { light: () => { if (Platform.OS !== "web") { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } }, medium: () => { if (Platform.OS !== "web") { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } }, heavy: () => { if (Platform.OS !== "web") { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy); } }, success: () => { if (Platform.OS !== "web") { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } }, error: () => { if (Platform.OS !== "web") { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); } }, }; // services/biometrics.ts import * as LocalAuthentication from "expo-local-authentication"; export async function authenticateWithBiometrics(): Promise<boolean> { const hasHardware = await LocalAuthentication.hasHardwareAsync(); if (!hasHardware) return false; const isEnrolled = await LocalAuthentication.isEnrolledAsync(); if (!isEnrolled) return false; const result = await LocalAuthentication.authenticateAsync({ promptMessage: "Authenticate to continue", fallbackLabel: "Use passcode", disableDeviceFallback: false, }); return result.success; } // services/notifications.ts import * as Notifications from "expo-notifications"; import { Platform } from "react-native"; import Constants from "expo-constants"; Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, }), }); export async function registerForPushNotifications() { let token: string | undefined; if (Platform.OS === "android") { await Notifications.setNotificationChannelAsync("default", { name: "default", importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], }); } const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== "granted") { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== "granted") { return null; } const projectId = Constants.expoConfig?.extra?.eas?.projectId; token = (await Notifications.getExpoPushTokenAsync({ projectId })).data; return token; } ``` ### Pattern 5: Platform-Specific Code ```typescript // components/ui/Button.tsx import { Platform, Pressable, StyleSheet, Text, ViewStyle } from 'react-native' import * as Haptics from 'expo-haptics' import Animated, { useAnimatedStyle, useSharedValue, withSpring, } from 'react-native-reanimated' const AnimatedPressable = Animated.createAnimatedComponent(Pressable) interface ButtonProps { title: string onPress: () => void variant?: 'primary' | 'secondary' | 'outline' disabled?: boolean } export function Button({ title, onPress, variant = 'primary', disabled = false, }: ButtonProps) { const scale = useSharedValue(1) const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })) const handlePressIn = () => { scale.value = withSpring(0.95) if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light) } } const handlePressOut = () => { scale.value = withSpring(1) } return ( <AnimatedPressable onPress={onPress} onPressIn={handlePressIn} onPressOut={handlePressOut} disabled={disabled} style={[ styles.button, styles[variant], disabled && styles.disabled, animatedStyle, ]} > <Text style={[styles.text, styles[`${variant}Text`]]}>{title}</Text> </AnimatedPressable> ) } // Platform-specific files // Button.ios.tsx - iOS-specific implementation // Button.android.tsx - Android-specific implementation // Button.web.tsx - Web-specific implementation // Or use Platform.select const styles = StyleSheet.create({ button: { paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8, alignItems: 'center', ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 4, }, }), }, primary: { backgroundColor: '#007AFF', }, secondary: { backgroundColor: '#5856D6', }, outline: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#007AFF', }, disabled: { opacity: 0.5, }, text: { fontSize: 16, fontWeight: '600', }, primaryText: { color: '#FFFFFF', }, secondaryText: { color: '#FFFFFF', }, outlineText: { color: '#007AFF', }, }) ``` ### Pattern 6: Performance Optimization ```typescript // components/ProductList.tsx import { FlashList } from '@shopify/flash-list' import { memo, useCallback } from 'react' interface ProductListProps { products: Product[] onProductPress: (id: string) => void } // Memoize list item const ProductItem = memo(function ProductItem({ item, onPress, }: { item: Product onPress: (id: string) => void }) { const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]) return ( <Pressable onPress={handlePress} style={styles.item}> <FastImage source={{ uri: item.image }} style={styles.image} resizeMode="cover" /> <Text style={styles.title}>{item.name}</Text> <Text style={styles.price}>${item.price}</Text> </Pressable> ) }) export function ProductList({ products, onProductPress }: ProductListProps) { const renderItem = useCallback( ({ item }: { item: Product }) => ( <ProductItem item={item} onPress={onProductPress} /> ), [onProductPress] ) const keyExtractor = useCallback((item: Product) => item.id, []) return ( <FlashList data={products} renderItem={renderItem} keyExtractor={keyExtractor} estimatedItemSize={100} // Performance optimizations removeClippedSubviews={true} maxToRenderPerBatch={10} windowSize={5} // Pull to refresh onRefresh={onRefresh} refreshing={isRefreshing} /> ) } ``` ## EAS Build & Submit ```json // eas.json { "cli": { "version": ">= 5.0.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal", "ios": { "simulator": true } }, "preview": { "distribution": "internal", "android": { "buildType": "apk" } }, "production": { "autoIncrement": true } }, "submit": { "production": { "ios": { "appleId": "your@email.com", "ascAppId": "123456789" }, "android": { "serviceAccountKeyPath": "./google-services.json" } } } } ``` ```bash # Build commands eas build --platform ios --profile development eas build --platform android --profile preview eas build --platform all --profile production # Submit to stores eas submit --platform ios eas submit --platform android # OTA updates eas update --branch production --message "Bug fixes" ``` ## Best Practices ### Do's - **Use Expo** - Faster development, OTA updates, managed native code - **FlashList over FlatList** - Better performance for long lists - **Memoize components** - Prevent unnecessary re-renders - **Use Reanimated** - 60fps animations on native thread - **Test on real devices** - Simulators miss real-world issues ### Don'ts - **Don't inline styles** - Use StyleSheet.create for performance - **Don't fetch in render** - Use useEffect or React Query - **Don't ignore platform differences** - Test on both iOS and Android - **Don't store secrets in code** - Use environment variables - **Don't skip error boundaries** - Mobile crashes are unforgiving ## Resources - [Expo Documentation](https://docs.expo.dev/) - [Expo Router](https://docs.expo.dev/router/introduction/) - [React Native Performance](https://reactnative.dev/docs/performance) - [FlashList](https://shopify.github.io/flash-list/)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

tailwind-design-system

Build scalable design systems with Tailwind CSS v4, design tokens,

coding
⭐1
# Tailwind Design System (v4) Build production-ready design systems with Tailwind CSS v4, including CSS-first configuration, design tokens, component variants, responsive patterns, and accessibility. > **Note**: This skill targets Tailwind CSS v4 (2024+). For v3 projects, refer to the [upgrade guide](https://tailwindcss.com/docs/upgrade-guide). ## When to Use This Skill - Creating a component library with Tailwind v4 - Implementing design tokens and theming with CSS-first configuration - Building responsive and accessible components - Standardizing UI patterns across a codebase - Migrating from Tailwind v3 to v4 - Setting up dark mode with native CSS features ## Key v4 Changes | v3 Pattern | v4 Pattern | | ------------------------------------- | --------------------------------------------------------------------- | | `tailwind.config.ts` | `@theme` in CSS | | `@tailwind base/components/utilities` | `@import "tailwindcss"` | | `darkMode: "class"` | `@custom-variant dark (&:where(.dark, .dark *))` | | `theme.extend.colors` | `@theme { --color-*: value }` | | `require("tailwindcss-animate")` | CSS `@keyframes` in `@theme` + `@starting-style` for entry animations | ## Quick Start ```css /* app.css - Tailwind v4 CSS-first configuration */ @import "tailwindcss"; /* Define your theme with @theme */ @theme { /* Semantic color tokens using OKLCH for better color perception */ --color-background: oklch(100% 0 0); --color-foreground: oklch(14.5% 0.025 264); --color-primary: oklch(14.5% 0.025 264); --color-primary-foreground: oklch(98% 0.01 264); --color-secondary: oklch(96% 0.01 264); --color-secondary-foreground: oklch(14.5% 0.025 264); --color-muted: oklch(96% 0.01 264); --color-muted-foreground: oklch(46% 0.02 264); --color-accent: oklch(96% 0.01 264); --color-accent-foreground: oklch(14.5% 0.025 264); --color-destructive: oklch(53% 0.22 27); --color-destructive-foreground: oklch(98% 0.01 264); --color-border: oklch(91% 0.01 264); --color-ring: oklch(14.5% 0.025 264); --color-card: oklch(100% 0 0); --color-card-foreground: oklch(14.5% 0.025 264); /* Ring offset for focus states */ --color-ring-offset: oklch(100% 0 0); /* Radius tokens */ --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; /* Animation tokens - keyframes inside @theme are output when referenced by --animate-* variables */ --animate-fade-in: fade-in 0.2s ease-out; --animate-fade-out: fade-out 0.2s ease-in; --animate-slide-in: slide-in 0.3s ease-out; --animate-slide-out: slide-out 0.3s ease-in; @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @keyframes slide-in { from { transform: translateY(-0.5rem); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes slide-out { from { transform: translateY(0); opacity: 1; } to { transform: translateY(-0.5rem); opacity: 0; } } } /* Dark mode variant - use @custom-variant for class-based dark mode */ @custom-variant dark (&:where(.dark, .dark *)); /* Dark mode theme overrides */ .dark { --color-background: oklch(14.5% 0.025 264); --color-foreground: oklch(98% 0.01 264); --color-primary: oklch(98% 0.01 264); --color-primary-foreground: oklch(14.5% 0.025 264); --color-secondary: oklch(22% 0.02 264); --color-secondary-foreground: oklch(98% 0.01 264); --color-muted: oklch(22% 0.02 264); --color-muted-foreground: oklch(65% 0.02 264); --color-accent: oklch(22% 0.02 264); --color-accent-foreground: oklch(98% 0.01 264); --color-destructive: oklch(42% 0.15 27); --color-destructive-foreground: oklch(98% 0.01 264); --color-border: oklch(22% 0.02 264); --color-ring: oklch(83% 0.02 264); --color-card: oklch(14.5% 0.025 264); --color-card-foreground: oklch(98% 0.01 264); --color-ring-offset: oklch(14.5% 0.025 264); } /* Base styles */ @layer base { * { @apply border-border; } body { @apply bg-background text-foreground antialiased; } } ``` ## Core Concepts ### 1. Design Token Hierarchy ``` Brand Tokens (abstract) └── Semantic Tokens (purpose) └── Component Tokens (specific) Example: oklch(45% 0.2 260) → --color-primary → bg-primary ``` ### 2. Component Architecture ``` Base styles → Variants → Sizes → States → Overrides ``` ## Patterns ### Pattern 1: CVA (Class Variance Authority) Components ```typescript // components/ui/button.tsx import { Slot } from '@radix-ui/react-slot' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' const buttonVariants = cva( // Base styles - v4 uses native CSS variables 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-border bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'size-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean } // React 19: No forwardRef needed export function Button({ className, variant, size, asChild = false, ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) { const Comp = asChild ? Slot : 'button' return ( <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> ) } // Usage <Button variant="destructive" size="lg">Delete</Button> <Button variant="outline">Cancel</Button> <Button asChild><Link href="/home">Home</Link></Button> ``` ### Pattern 2: Compound Components (React 19) ```typescript // components/ui/card.tsx import { cn } from '@/lib/utils' // React 19: ref is a regular prop, no forwardRef export function Card({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) { return ( <div ref={ref} className={cn( 'rounded-lg border border-border bg-card text-card-foreground shadow-sm', className )} {...props} /> ) } export function CardHeader({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) { return ( <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} /> ) } export function CardTitle({ className, ref, ...props }: React.HTMLAttributes<HTMLHeadingElement> & { ref?: React.Ref<HTMLHeadingElement> }) { return ( <h3 ref={ref} className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} /> ) } export function CardDescription({ className, ref, ...props }: React.HTMLAttributes<HTMLParagraphElement> & { ref?: React.Ref<HTMLParagraphElement> }) { return ( <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} /> ) } export function CardContent({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) { return ( <div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> ) } export function CardFooter({ className, ref, ...props }: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> }) { return ( <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} /> ) } // Usage <Card> <CardHeader> <CardTitle>Account</CardTitle> <CardDescription>Manage your account settings</CardDescription> </CardHeader> <CardContent> <form>...</form> </CardContent> <CardFooter> <Button>Save</Button> </CardFooter> </Card> ``` ### Pattern 3: Form Components ```typescript // components/ui/input.tsx import { cn } from '@/lib/utils' export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { error?: string ref?: React.Ref<HTMLInputElement> } export function Input({ className, type, error, ref, ...props }: InputProps) { return ( <div className="relative"> <input type={type} className={cn( 'flex h-10 w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', error && 'border-destructive focus-visible:ring-destructive', className )} ref={ref} aria-invalid={!!error} aria-describedby={error ? `${props.id}-error` : undefined} {...props} /> {error && ( <p id={`${props.id}-error`} className="mt-1 text-sm text-destructive" role="alert" > {error} </p> )} </div> ) } // components/ui/label.tsx import { cva, type VariantProps } from 'class-variance-authority' const labelVariants = cva( 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' ) export function Label({ className, ref, ...props }: React.LabelHTMLAttributes<HTMLLabelElement> & { ref?: React.Ref<HTMLLabelElement> }) { return ( <label ref={ref} className={cn(labelVariants(), className)} {...props} /> ) } // Usage with React Hook Form + Zod import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const schema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), }) function LoginForm() { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }) return ( <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div className="space-y-2"> <Label htmlFor="email">Email</Label> <Input id="email" type="email" {...register('email')} error={errors.email?.message} /> </div> <div className="space-y-2"> <Label htmlFor="password">Password</Label> <Input id="password" type="password" {...register('password')} error={errors.password?.message} /> </div> <Button type="submit" className="w-full">Sign In</Button> </form> ) } ``` ### Pattern 4: Responsive Grid System ```typescript // components/ui/grid.tsx import { cn } from '@/lib/utils' import { cva, type VariantProps } from 'class-variance-authority' const gridVariants = cva('grid', { variants: { cols: { 1: 'grid-cols-1', 2: 'grid-cols-1 sm:grid-cols-2', 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3', 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4', 5: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-5', 6: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6', }, gap: { none: 'gap-0', sm: 'gap-2', md: 'gap-4', lg: 'gap-6', xl: 'gap-8', }, }, defaultVariants: { cols: 3, gap: 'md', }, }) interface GridProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof gridVariants> {} export function Grid({ className, cols, gap, ...props }: GridProps) { return ( <div className={cn(gridVariants({ cols, gap, className }))} {...props} /> ) } // Container component const containerVariants = cva('mx-auto w-full px-4 sm:px-6 lg:px-8', { variants: { size: { sm: 'max-w-screen-sm', md: 'max-w-screen-md', lg: 'max-w-screen-lg', xl: 'max-w-screen-xl', '2xl': 'max-w-screen-2xl', full: 'max-w-full', }, }, defaultVariants: { size: 'xl', }, }) interface ContainerProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof containerVariants> {} export function Container({ className, size, ...props }: ContainerProps) { return ( <div className={cn(containerVariants({ size, className }))} {...props} /> ) } // Usage <Container> <Grid cols={4} gap="lg"> {products.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </Grid> </Container> ``` ### Pattern 5: Native CSS Animations (v4) ```css /* In your CSS file - native @starting-style for entry animations */ @theme { --animate-dialog-in: dialog-fade-in 0.2s ease-out; --animate-dialog-out: dialog-fade-out 0.15s ease-in; } @keyframes dialog-fade-in { from { opacity: 0; transform: scale(0.95) translateY(-0.5rem); } to { opacity: 1; transform: scale(1) translateY(0); } } @keyframes dialog-fade-out { from { opacity: 1; transform: scale(1) translateY(0); } to { opacity: 0; transform: scale(0.95) translateY(-0.5rem); } } /* Native popover animations using @starting-style */ [popover] { transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete; opacity: 0; transform: scale(0.95); } [popover]:popover-open { opacity: 1; transform: scale(1); } @starting-style { [popover]:popover-open { opacity: 0; transform: scale(0.95); } } ``` ```typescript // components/ui/dialog.tsx - Using native popover API import * as DialogPrimitive from '@radix-ui/react-dialog' import { cn } from '@/lib/utils' const DialogPortal = DialogPrimitive.Portal export function DialogOverlay({ className, ref, ...props }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & { ref?: React.Ref<HTMLDivElement> }) { return ( <DialogPrimitive.Overlay ref={ref} className={cn( 'fixed inset-0 z-50 bg-black/80', 'data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out', className )} {...props} /> ) } export function DialogContent({ className, children, ref, ...props }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { ref?: React.Ref<HTMLDivElement> }) { return ( <DialogPortal> <DialogOverlay /> <DialogPrimitive.Content ref={ref} className={cn( 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-border bg-background p-6 shadow-lg sm:rounded-lg', 'data-[state=open]:animate-dialog-in data-[state=closed]:animate-dialog-out', className )} {...props} > {children} </DialogPrimitive.Content> </DialogPortal> ) } ``` ### Pattern 6: Dark Mode with CSS (v4) ```typescript // providers/ThemeProvider.tsx - Simplified for v4 'use client' import { createContext, useContext, useEffect, useState } from 'react' type Theme = 'dark' | 'light' | 'system' interface ThemeContextType { theme: Theme setTheme: (theme: Theme) => void resolvedTheme: 'dark' | 'light' } const ThemeContext = createContext<ThemeContextType | undefined>(undefined) export function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'theme', }: { children: React.ReactNode defaultTheme?: Theme storageKey?: string }) { const [theme, setTheme] = useState<Theme>(defaultTheme) const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('light') useEffect(() => { const stored = localStorage.getItem(storageKey) as Theme | null if (stored) setTheme(stored) }, [storageKey]) useEffect(() => { const root = document.documentElement root.classList.remove('light', 'dark') const resolved = theme === 'system' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme root.classList.add(resolved) setResolvedTheme(resolved) // Update meta theme-color for mobile browsers const metaThemeColor = document.querySelector('meta[name="theme-color"]') if (metaThemeColor) { metaThemeColor.setAttribute('content', resolved === 'dark' ? '#09090b' : '#ffffff') } }, [theme]) return ( <ThemeContext.Provider value={{ theme, setTheme: (newTheme) => { localStorage.setItem(storageKey, newTheme) setTheme(newTheme) }, resolvedTheme, }}> {children} </ThemeContext.Provider> ) } export const useTheme = () => { const context = useContext(ThemeContext) if (!context) throw new Error('useTheme must be used within ThemeProvider') return context } // components/ThemeToggle.tsx import { Moon, Sun } from 'lucide-react' import { useTheme } from '@/providers/ThemeProvider' export function ThemeToggle() { const { resolvedTheme, setTheme } = useTheme() return ( <Button variant="ghost" size="icon" onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')} > <Sun className="size-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> <Moon className="absolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> <span className="sr-only">Toggle theme</span> </Button> ) } ``` ## Utility Functions ```typescript // lib/utils.ts import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } // Focus ring utility export const focusRing = cn( "focus-visible:outline-none focus-visible:ring-2", "focus-visible:ring-ring focus-visible:ring-offset-2", ); // Disabled utility export const disabled = "disabled:pointer-events-none disabled:opacity-50"; ``` ## Advanced v4 Patterns ### Custom Utilities with `@utility` Define reusable custom utilities: ```css /* Custom utility for decorative lines */ @utility line-t { @apply relative before:absolute before:top-0 before:-left-[100vw] before:h-px before:w-[200vw] before:bg-gray-950/5 dark:before:bg-white/10; } /* Custom ut
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

competitive-landscape

This skill should be used when the user asks to "analyze

business
⭐1
# Competitive Landscape Analysis Comprehensive frameworks for analyzing competition, identifying differentiation opportunities, and developing winning market positioning strategies. ## Overview Understand competitive dynamics using proven frameworks (Porter's Five Forces, Blue Ocean Strategy, positioning maps) to identify opportunities and craft defensible competitive advantages. ## Porter's Five Forces Analyze industry attractiveness and competitive intensity. ### Force 1: Threat of New Entrants **Barriers to Entry:** - Capital requirements - Economies of scale - Switching costs - Brand loyalty - Regulatory barriers - Access to distribution - Network effects **High Threat:** Low barriers, easy to enter (e.g., simple SaaS tools) **Low Threat:** High barriers (e.g., regulated industries, hardware) **Analysis Questions:** - How easy is it for new competitors to enter? - What would it cost to launch a competing product? - Are there network effects or switching costs protecting incumbents? ### Force 2: Bargaining Power of Suppliers **Supplier Power Factors:** - Supplier concentration - Availability of substitutes - Importance to supplier - Switching costs - Forward integration threat **High Power:** Few suppliers, critical inputs (e.g., cloud infrastructure providers) **Low Power:** Many alternatives, commoditized (e.g., generic services) **Analysis Questions:** - Who are our critical suppliers? - Could they raise prices or reduce quality? - Can we switch suppliers easily? ### Force 3: Bargaining Power of Buyers **Buyer Power Factors:** - Buyer concentration - Volume purchased - Product differentiation - Price sensitivity - Backward integration threat **High Power:** Few large customers, standardized products (e.g., enterprise deals) **Low Power:** Many small customers, differentiated product (e.g., consumer subscriptions) **Analysis Questions:** - Can customers easily switch to competitors? - Do few customers generate most revenue? - How price-sensitive are buyers? ### Force 4: Threat of Substitutes **Substitute Considerations:** - Alternative solutions - Price-performance tradeoff - Switching costs - Buyer propensity to substitute **High Threat:** Many alternatives, low switching cost (e.g., productivity software) **Low Threat:** Unique solution, high switching cost (e.g., ERP systems) **Analysis Questions:** - What alternative ways can customers solve this problem? - How do substitutes compare on price and performance? - What's the cost to switch to a substitute? ### Force 5: Competitive Rivalry **Rivalry Intensity Factors:** - Number of competitors - Industry growth rate - Product differentiation - Exit barriers - Strategic stakes **High Rivalry:** Many competitors, slow growth, commoditized (e.g., email marketing) **Low Rivalry:** Few competitors, fast growth, differentiated (e.g., emerging AI tools) **Analysis Questions:** - How many direct competitors exist? - Is the market growing or stagnant? - How differentiated are offerings? - Are competitors competing on price or value? ### Forces Analysis Summary Create a scorecard: | Force | Intensity (1-5) | Impact | Key Factors | | -------------- | --------------- | ------ | --------------------------------- | | New Entrants | 3 | Medium | Low barriers but network effects | | Supplier Power | 2 | Low | Many cloud providers | | Buyer Power | 4 | High | Enterprise customers concentrated | | Substitutes | 3 | Medium | Manual processes alternative | | Rivalry | 4 | High | 10+ direct competitors | **Overall Assessment:** Moderate industry attractiveness with high rivalry and buyer power ## Blue Ocean Strategy Identify uncontested market space through value innovation. ### Four Actions Framework **Eliminate:** What factors can be eliminated that the industry takes for granted? **Reduce:** What factors can be reduced well below industry standard? **Raise:** What factors can be raised well above industry standard? **Create:** What factors can be created that the industry never offered? ### Strategy Canvas Map your offering vs. competitors on key factors. **Example: Budget Hotels** ``` High | ★ Traditional Hotels | ★ Budget Hotels (new) | Low |___________________________________ Price Luxury Convenience Cleanliness Budget Hotel Strategy: - Eliminate: Luxury amenities, room service - Reduce: Lobby size, staff - Raise: Cleanliness, online booking - Create: Self-service kiosks, mobile app ``` ### Value Innovation Find the sweet spot: Lower cost + higher value **Steps:** 1. Map industry competing factors 2. Identify factors to eliminate/reduce (cost savings) 3. Identify factors to raise/create (differentiation) 4. Validate that combination creates new market space ## Competitive Positioning ### Positioning Map Plot competitors on 2-3 key dimensions. **Example Dimensions:** - Price vs. Features - Complexity vs. Ease of Use - Enterprise vs. SMB Focus - Self-Service vs. High-Touch - Generalist vs. Specialist **How to Create:** 1. Choose 2 dimensions most important to customers 2. Plot all competitors 3. Identify gaps (white space) 4. Validate gap represents real customer need **Example:** ``` High Price | | ★ Enterprise A ★ Enterprise B | | ● Our Position (gap) | | ★ Competitor C ★ Competitor D | Low Price |____________________________________________ Simple Complex ``` ### Differentiation Strategy **How to Differentiate:** 1. **Product Differentiation** - Unique features - Superior performance - Better design/UX - Integration ecosystem 2. **Service Differentiation** - Customer support quality - Onboarding experience - Response time - Success programs 3. **Brand Differentiation** - Trust and reputation - Thought leadership - Community - Values alignment 4. **Price Differentiation** - Premium positioning - Value positioning - Transparent pricing - Flexible packaging ### Positioning Statement Framework ``` For [target customer] Who [statement of need or opportunity] Our product is [product category] That [statement of key benefit] Unlike [primary competitive alternative] Our product [statement of primary differentiation] ``` **Example:** ``` For e-commerce companies Who struggle with email marketing automation Our product is an AI-powered email platform That increases conversion rates by 40% Unlike Klaviyo and Mailchimp Our product uses AI to personalize at scale ``` ## Competitive Intelligence ### Information Gathering **Public Sources:** - Company websites and blogs - Press releases and news - Job postings (hint at strategy) - Customer reviews (G2, Capterra) - Social media and forums - Glassdoor (employee insights) - SEC filings (public companies) - Patent filings **Direct Research:** - Customer interviews - Win/loss analysis - Sales team feedback - Product demos and trials - Conference attendance ### Competitor Profile Template For each key competitor, document: **Company Overview:** - Founded, HQ, funding, size - Leadership team - Company stage and trajectory **Product:** - Core features - Target customers - Pricing and packaging - Technology stack - Recent launches **Go-to-Market:** - Sales model (self-serve, sales-led) - Marketing strategy - Distribution channels - Partnerships **Strengths:** - What they do better than anyone - Key competitive advantages - Market position **Weaknesses:** - Gaps in product - Customer complaints - Operational challenges **Strategy:** - Stated direction - Inferred priorities - Likely next moves ## Competitive Pricing Analysis ### Price Positioning **Premium (Top 25%):** - Superior product/service - Strong brand - High-touch sales - Enterprise focus **Mid-Market (Middle 50%):** - Balanced value - Standard features - Mixed sales model - Broad market **Value (Bottom 25%):** - Basic functionality - Self-service - Cost leadership - High volume, low margin ### Pricing Comparison Matrix | Competitor | Entry Price | Mid Tier | Enterprise | Model | | ------------ | ----------- | -------- | ---------- | ------------ | | Competitor A | $29/mo | $99/mo | Custom | Subscription | | Competitor B | $49/mo | $199/mo | $499/mo | Subscription | | Us | $39/mo | $129/mo | Custom | Subscription | **Analysis:** - Are we priced competitively? - What does our pricing signal? - Are there gaps in our packaging? ## Go-to-Market Strategy ### Market Entry Strategies **Direct Competition:** - Head-to-head against established players - Requires differentiation and resources - Example: Better features at lower price **Niche Focus:** - Target underserved segment - Become specialist vs. generalist - Example: "Salesforce for real estate" **Disruptive Innovation:** - Target non-consumers or low end - Improve over time to move upmarket - Example: Freemium model disrupting enterprise **Platform Play:** - Build ecosystem and network effects - Aggregate complementary services - Example: Marketplace or API platform ### Beachhead Market **Characteristics of Good Beachhead:** - Specific, reachable segment - Acute pain you solve well - Limited competition - Willing to pay - Can lead to expansion **Example:** Instead of "project management software", target "project management for construction teams" ## Competitive Advantage ### Sustainable Advantages **Network Effects:** - Value increases with users - Example: Slack, marketplaces **Switching Costs:** - High cost to change - Example: CRM systems with data **Economies of Scale:** - Unit costs decrease with volume - Example: Cloud infrastructure **Brand:** - Trust and reputation - Example: Security software **Proprietary Technology:** - Patents or trade secrets - Example: Algorithms, data **Regulatory:** - Licenses or approvals - Example: Fintech, healthcare ### Testing Your Advantage Ask: - Can competitors copy this in < 2 years? - Does this matter to customers? - Do we execute this better than anyone? - Is this advantage durable? If "no" to any, it's not a sustainable advantage. ## Competitive Monitoring ### What to Track **Product Changes:** - New features - Pricing changes - Packaging adjustments **Market Signals:** - Funding announcements - Key hires (especially leadership) - Customer wins/losses - Partnerships **Performance Metrics:** - Revenue (if public or disclosed) - Customer count - Growth rate - Market share estimates ### Monitoring Cadence **Weekly:** - Product release notes - News mentions **Monthly:** - Win/loss analysis review - Positioning map updates **Quarterly:** - Deep competitive review - Strategy adjustment **Annually:** - Major strategy reassessment - Market trends analysis ## Additional Resources ### Reference Files - **`references/frameworks-deep-dive.md`** - Detailed application of each framework with worksheets - **`references/intel-sources.md`** - Comprehensive list of competitive intelligence sources ### Example Files - **`examples/competitor-analysis.md`** - Complete competitive analysis for a SaaS startup - **`examples/positioning-workshop.md`** - Step-by-step positioning development process ## Quick Start To analyze competitive landscape: 1. **Identify competitors** - Direct, indirect, and future threats 2. **Apply Porter's Five Forces** - Assess industry attractiveness 3. **Create positioning map** - Visualize competitive space 4. **Profile top 3-5 competitors** - Deep dive on key rivals 5. **Identify differentiation** - What makes you unique 6. **Analyze pricing** - Where do you fit? 7. **Assess advantages** - What's defensible? 8. **Develop strategy** - How to win For detailed frameworks and examples, see `references/` and `examples/`.
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

accessibility-compliance

Implement WCAG 2.2 compliant interfaces with mobile accessibility,

coding
⭐1
# Accessibility Compliance Master accessibility implementation to create inclusive experiences that work for everyone, including users with disabilities. ## When to Use This Skill - Implementing WCAG 2.2 Level AA or AAA compliance - Building screen reader accessible interfaces - Adding keyboard navigation to interactive components - Implementing focus management and focus trapping - Creating accessible forms with proper labeling - Supporting reduced motion and high contrast preferences - Building mobile accessibility features (iOS VoiceOver, Android TalkBack) - Conducting accessibility audits and fixing violations ## Core Capabilities ### 1. WCAG 2.2 Guidelines - Perceivable: Content must be presentable in different ways - Operable: Interface must be navigable with keyboard and assistive tech - Understandable: Content and operation must be clear - Robust: Content must work with current and future assistive technologies ### 2. ARIA Patterns - Roles: Define element purpose (button, dialog, navigation) - States: Indicate current condition (expanded, selected, disabled) - Properties: Describe relationships and additional info (labelledby, describedby) - Live regions: Announce dynamic content changes ### 3. Keyboard Navigation - Focus order and tab sequence - Focus indicators and visible focus states - Keyboard shortcuts and hotkeys - Focus trapping for modals and dialogs ### 4. Screen Reader Support - Semantic HTML structure - Alternative text for images - Proper heading hierarchy - Skip links and landmarks ### 5. Mobile Accessibility - Touch target sizing (44x44dp minimum) - VoiceOver and TalkBack compatibility - Gesture alternatives - Dynamic Type support ## Quick Reference ### WCAG 2.2 Success Criteria Checklist | Level | Criterion | Description | | ----- | --------- | ---------------------------------------------------- | | A | 1.1.1 | Non-text content has text alternatives | | A | 1.3.1 | Info and relationships programmatically determinable | | A | 2.1.1 | All functionality keyboard accessible | | A | 2.4.1 | Skip to main content mechanism | | AA | 1.4.3 | Contrast ratio 4.5:1 (text), 3:1 (large text) | | AA | 1.4.11 | Non-text contrast 3:1 | | AA | 2.4.7 | Focus visible | | AA | 2.5.8 | Target size minimum 24x24px (NEW in 2.2) | | AAA | 1.4.6 | Enhanced contrast 7:1 | | AAA | 2.5.5 | Target size minimum 44x44px | ## Key Patterns ### Pattern 1: Accessible Button ```tsx interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { variant?: "primary" | "secondary"; isLoading?: boolean; } function AccessibleButton({ children, variant = "primary", isLoading = false, disabled, ...props }: ButtonProps) { return ( <button // Disable when loading disabled={disabled || isLoading} // Announce loading state to screen readers aria-busy={isLoading} // Describe the button's current state aria-disabled={disabled || isLoading} className={cn( // Visible focus ring "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", // Minimum touch target size (44x44px) "min-h-[44px] min-w-[44px]", variant === "primary" && "bg-primary text-primary-foreground", (disabled || isLoading) && "opacity-50 cursor-not-allowed", )} {...props} > {isLoading ? ( <> <span className="sr-only">Loading</span> <Spinner aria-hidden="true" /> </> ) : ( children )} </button> ); } ``` ### Pattern 2: Accessible Modal Dialog ```tsx import * as React from "react"; import { FocusTrap } from "@headlessui/react"; interface DialogProps { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode; } function AccessibleDialog({ isOpen, onClose, title, children }: DialogProps) { const titleId = React.useId(); const descriptionId = React.useId(); // Close on Escape key React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) { onClose(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [isOpen, onClose]); // Prevent body scroll when open React.useEffect(() => { if (isOpen) { document.body.style.overflow = "hidden"; } return () => { document.body.style.overflow = ""; }; }, [isOpen]); if (!isOpen) return null; return ( <div role="dialog" aria-modal="true" aria-labelledby={titleId} aria-describedby={descriptionId} > {/* Backdrop */} <div className="fixed inset-0 bg-black/50" aria-hidden="true" onClick={onClose} /> {/* Focus trap container */} <FocusTrap> <div className="fixed inset-0 flex items-center justify-center p-4"> <div className="bg-background rounded-lg shadow-lg max-w-md w-full p-6"> <h2 id={titleId} className="text-lg font-semibold"> {title} </h2> <div id={descriptionId}>{children}</div> <button onClick={onClose} className="absolute top-4 right-4" aria-label="Close dialog" > <X className="h-4 w-4" /> </button> </div> </div> </FocusTrap> </div> ); } ``` ### Pattern 3: Accessible Form ```tsx function AccessibleForm() { const [errors, setErrors] = React.useState<Record<string, string>>({}); return ( <form aria-describedby="form-errors" noValidate> {/* Error summary for screen readers */} {Object.keys(errors).length > 0 && ( <div id="form-errors" role="alert" aria-live="assertive" className="bg-destructive/10 border border-destructive p-4 rounded-md mb-4" > <h2 className="font-semibold text-destructive"> Please fix the following errors: </h2> <ul className="list-disc list-inside mt-2"> {Object.entries(errors).map(([field, message]) => ( <li key={field}> <a href={`#${field}`} className="underline"> {message} </a> </li> ))} </ul> </div> )} {/* Required field with error */} <div className="space-y-2"> <label htmlFor="email" className="block font-medium"> Email address <span aria-hidden="true" className="text-destructive ml-1"> * </span> <span className="sr-only">(required)</span> </label> <input id="email" name="email" type="email" required aria-required="true" aria-invalid={!!errors.email} aria-describedby={errors.email ? "email-error" : "email-hint"} className={cn( "w-full px-3 py-2 border rounded-md", errors.email && "border-destructive", )} /> {errors.email ? ( <p id="email-error" className="text-sm text-destructive" role="alert"> {errors.email} </p> ) : ( <p id="email-hint" className="text-sm text-muted-foreground"> We'll never share your email. </p> )} </div> <button type="submit" className="mt-4"> Submit </button> </form> ); } ``` ### Pattern 4: Skip Navigation Link ```tsx function SkipLink() { return ( <a href="#main-content" className={cn( // Hidden by default, visible on focus "sr-only focus:not-sr-only", "focus:absolute focus:top-4 focus:left-4 focus:z-50", "focus:bg-background focus:px-4 focus:py-2 focus:rounded-md", "focus:ring-2 focus:ring-primary", )} > Skip to main content </a> ); } // In layout function Layout({ children }) { return ( <> <SkipLink /> <header>...</header> <nav aria-label="Main navigation">...</nav> <main id="main-content" tabIndex={-1}> {children} </main> <footer>...</footer> </> ); } ``` ### Pattern 5: Live Region for Announcements ```tsx function useAnnounce() { const [message, setMessage] = React.useState(""); const announce = React.useCallback( (text: string, priority: "polite" | "assertive" = "polite") => { setMessage(""); // Clear first to ensure re-announcement setTimeout(() => setMessage(text), 100); }, [], ); const Announcer = () => ( <div role="status" aria-live="polite" aria-atomic="true" className="sr-only" > {message} </div> ); return { announce, Announcer }; } // Usage function SearchResults({ results, isLoading }) { const { announce, Announcer } = useAnnounce(); React.useEffect(() => { if (!isLoading && results) { announce(`${results.length} results found`); } }, [results, isLoading, announce]); return ( <> <Announcer /> <ul>{/* results */}</ul> </> ); } ``` ## Color Contrast Requirements ```typescript // Contrast ratio utilities function getContrastRatio(foreground: string, background: string): number { const fgLuminance = getLuminance(foreground); const bgLuminance = getLuminance(background); const lighter = Math.max(fgLuminance, bgLuminance); const darker = Math.min(fgLuminance, bgLuminance); return (lighter + 0.05) / (darker + 0.05); } // WCAG requirements const CONTRAST_REQUIREMENTS = { // Normal text (<18pt or <14pt bold) normalText: { AA: 4.5, AAA: 7, }, // Large text (>=18pt or >=14pt bold) largeText: { AA: 3, AAA: 4.5, }, // UI components and graphics uiComponents: { AA: 3, }, }; ``` ## Best Practices 1. **Use Semantic HTML**: Prefer native elements over ARIA when possible 2. **Test with Real Users**: Include people with disabilities in user testing 3. **Keyboard First**: Design interactions to work without a mouse 4. **Don't Disable Focus Styles**: Style them, don't remove them 5. **Provide Text Alternatives**: All non-text content needs descriptions 6. **Support Zoom**: Content should work at 200% zoom 7. **Announce Changes**: Use live regions for dynamic content 8. **Respect Preferences**: Honor prefers-reduced-motion and prefers-contrast ## Common Issues - **Missing alt text**: Images without descriptions - **Poor color contrast**: Text hard to read against background - **Keyboard traps**: Focus stuck in component - **Missing labels**: Form inputs without associated labels - **Auto-playing media**: Content that plays without user initiation - **Inaccessible custom controls**: Recreating native functionality poorly - **Missing skip links**: No way to bypass repetitive content - **Focus order issues**: Tab order doesn't match visual order ## Testing Tools - **Automated**: axe DevTools, WAVE, Lighthouse - **Manual**: VoiceOver (macOS/iOS), NVDA/JAWS (Windows), TalkBack (Android) - **Simulators**: NoCoffee (vision), Silktide (various disabilities) ## Resources - [WCAG 2.2 Guidelines](https://www.w3.org/WAI/WCAG22/quickref/) - [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) - [A11y Project Checklist](https://www.a11yproject.com/checklist/) - [Inclusive Components](https://inclusive-components.design/) - [Deque University](https://dequeuniversity.com/)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

design-system-patterns

Build scalable design systems with design tokens, theming

coding
⭐1
# Design System Patterns Master design system architecture to create consistent, maintainable, and scalable UI foundations across web and mobile applications. ## When to Use This Skill - Creating design tokens for colors, typography, spacing, and shadows - Implementing light/dark theme switching with CSS custom properties - Building multi-brand theming systems - Architecting component libraries with consistent APIs - Establishing design-to-code workflows with Figma tokens - Creating semantic token hierarchies (primitive, semantic, component) - Setting up design system documentation and guidelines ## Core Capabilities ### 1. Design Tokens - Primitive tokens (raw values: colors, sizes, fonts) - Semantic tokens (contextual meaning: text-primary, surface-elevated) - Component tokens (specific usage: button-bg, card-border) - Token naming conventions and organization - Multi-platform token generation (CSS, iOS, Android) ### 2. Theming Infrastructure - CSS custom properties architecture - Theme context providers in React - Dynamic theme switching - System preference detection (prefers-color-scheme) - Persistent theme storage - Reduced motion and high contrast modes ### 3. Component Architecture - Compound component patterns - Polymorphic components (as prop) - Variant and size systems - Slot-based composition - Headless UI patterns - Style props and responsive variants ### 4. Token Pipeline - Figma to code synchronization - Style Dictionary configuration - Token transformation and formatting - CI/CD integration for token updates ## Quick Start ```typescript // Design tokens with CSS custom properties const tokens = { colors: { // Primitive tokens gray: { 50: "#fafafa", 100: "#f5f5f5", 900: "#171717", }, blue: { 500: "#3b82f6", 600: "#2563eb", }, }, // Semantic tokens (reference primitives) semantic: { light: { "text-primary": "var(--color-gray-900)", "text-secondary": "var(--color-gray-600)", "surface-default": "var(--color-white)", "surface-elevated": "var(--color-gray-50)", "border-default": "var(--color-gray-200)", "interactive-primary": "var(--color-blue-500)", }, dark: { "text-primary": "var(--color-gray-50)", "text-secondary": "var(--color-gray-400)", "surface-default": "var(--color-gray-900)", "surface-elevated": "var(--color-gray-800)", "border-default": "var(--color-gray-700)", "interactive-primary": "var(--color-blue-400)", }, }, }; ``` ## Key Patterns ### Pattern 1: Token Hierarchy ```css /* Layer 1: Primitive tokens (raw values) */ :root { --color-blue-500: #3b82f6; --color-blue-600: #2563eb; --color-gray-50: #fafafa; --color-gray-900: #171717; --space-1: 0.25rem; --space-2: 0.5rem; --space-4: 1rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --radius-sm: 0.25rem; --radius-md: 0.5rem; --radius-lg: 1rem; } /* Layer 2: Semantic tokens (meaning) */ :root { --text-primary: var(--color-gray-900); --text-secondary: var(--color-gray-600); --surface-default: white; --interactive-primary: var(--color-blue-500); --interactive-primary-hover: var(--color-blue-600); } /* Layer 3: Component tokens (specific usage) */ :root { --button-bg: var(--interactive-primary); --button-bg-hover: var(--interactive-primary-hover); --button-text: white; --button-radius: var(--radius-md); --button-padding-x: var(--space-4); --button-padding-y: var(--space-2); } ``` ### Pattern 2: Theme Switching with React ```tsx import { createContext, useContext, useEffect, useState } from "react"; type Theme = "light" | "dark" | "system"; interface ThemeContextValue { theme: Theme; resolvedTheme: "light" | "dark"; setTheme: (theme: Theme) => void; } const ThemeContext = createContext<ThemeContextValue | null>(null); export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<Theme>(() => { if (typeof window !== "undefined") { return (localStorage.getItem("theme") as Theme) || "system"; } return "system"; }); const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light"); useEffect(() => { const root = document.documentElement; const applyTheme = (isDark: boolean) => { root.classList.remove("light", "dark"); root.classList.add(isDark ? "dark" : "light"); setResolvedTheme(isDark ? "dark" : "light"); }; if (theme === "system") { const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); applyTheme(mediaQuery.matches); const handler = (e: MediaQueryListEvent) => applyTheme(e.matches); mediaQuery.addEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler); } else { applyTheme(theme === "dark"); } }, [theme]); useEffect(() => { localStorage.setItem("theme", theme); }, [theme]); return ( <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}> {children} </ThemeContext.Provider> ); } export const useTheme = () => { const context = useContext(ThemeContext); if (!context) throw new Error("useTheme must be used within ThemeProvider"); return context; }; ``` ### Pattern 3: Variant System with CVA ```tsx import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( // Base styles "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { sm: "h-9 px-3 text-sm", md: "h-10 px-4 text-sm", lg: "h-11 px-8 text-base", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "md", }, }, ); interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean; } export function Button({ className, variant, size, ...props }: ButtonProps) { return ( <button className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); } ``` ### Pattern 4: Style Dictionary Configuration ```javascript // style-dictionary.config.js module.exports = { source: ["tokens/**/*.json"], platforms: { css: { transformGroup: "css", buildPath: "dist/css/", files: [ { destination: "variables.css", format: "css/variables", options: { outputReferences: true, // Preserve token references }, }, ], }, scss: { transformGroup: "scss", buildPath: "dist/scss/", files: [ { destination: "_variables.scss", format: "scss/variables", }, ], }, ios: { transformGroup: "ios-swift", buildPath: "dist/ios/", files: [ { destination: "DesignTokens.swift", format: "ios-swift/class.swift", className: "DesignTokens", }, ], }, android: { transformGroup: "android", buildPath: "dist/android/", files: [ { destination: "colors.xml", format: "android/colors", filter: { attributes: { category: "color" } }, }, ], }, }, }; ``` ## Best Practices 1. **Name Tokens by Purpose**: Use semantic names (text-primary) not visual descriptions (dark-gray) 2. **Maintain Token Hierarchy**: Primitives > Semantic > Component tokens 3. **Document Token Usage**: Include usage guidelines with token definitions 4. **Version Tokens**: Treat token changes as API changes with semver 5. **Test Theme Combinations**: Verify all themes work with all components 6. **Automate Token Pipeline**: CI/CD for Figma-to-code synchronization 7. **Provide Migration Paths**: Deprecate tokens gradually with clear alternatives ## Common Issues - **Token Sprawl**: Too many tokens without clear hierarchy - **Inconsistent Naming**: Mixed conventions (camelCase vs kebab-case) - **Missing Dark Mode**: Tokens that don't adapt to theme changes - **Hardcoded Values**: Using raw values instead of tokens - **Circular References**: Tokens referencing each other in loops - **Platform Gaps**: Tokens missing for some platforms (web but not mobile) ## Resources - [Style Dictionary Documentation](https://amzn.github.io/style-dictionary/) - [Tokens Studio for Figma](https://tokens.studio/) - [Design Tokens W3C Spec](https://design-tokens.github.io/community-group/format/) - [Radix UI Themes](https://www.radix-ui.com/themes) - [shadcn/ui Theming](https://ui.shadcn.com/docs/theming)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

mobile-android-design

Master Material Design 3 and Jetpack Compose patterns for building

coding
⭐1
# Android Mobile Design Master Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem. ## When to Use This Skill - Designing Android app interfaces following Material Design 3 - Building Jetpack Compose UI and layouts - Implementing Android navigation patterns (Navigation Compose) - Creating adaptive layouts for phones, tablets, and foldables - Using Material 3 theming with dynamic colors - Building accessible Android interfaces - Implementing Android-specific gestures and interactions - Designing for different screen configurations ## Core Concepts ### 1. Material Design 3 Principles **Personalization**: Dynamic color adapts UI to user's wallpaper **Accessibility**: Tonal palettes ensure sufficient color contrast **Large Screens**: Responsive layouts for tablets and foldables **Material Components:** - Cards, Buttons, FABs, Chips - Navigation (rail, drawer, bottom nav) - Text fields, Dialogs, Sheets - Lists, Menus, Progress indicators ### 2. Jetpack Compose Layout System **Column and Row:** ```kotlin // Vertical arrangement with alignment Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.Start ) { Text( text = "Title", style = MaterialTheme.typography.headlineSmall ) Text( text = "Subtitle", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } // Horizontal arrangement with weight Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.Star, contentDescription = null) Text("Featured") Spacer(modifier = Modifier.weight(1f)) TextButton(onClick = {}) { Text("View All") } } ``` **Lazy Lists and Grids:** ```kotlin // Lazy column with sticky headers LazyColumn { items.groupBy { it.category }.forEach { (category, categoryItems) -> stickyHeader { Text( text = category, modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.surface) .padding(16.dp), style = MaterialTheme.typography.titleMedium ) } items(categoryItems) { item -> ItemRow(item = item) } } } // Adaptive grid LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 150.dp), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(items) { item -> ItemCard(item = item) } } ``` ### 3. Navigation Patterns **Bottom Navigation:** ```kotlin @Composable fun MainScreen() { val navController = rememberNavController() Scaffold( bottomBar = { NavigationBar { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination NavigationDestination.entries.forEach { destination -> NavigationBarItem( icon = { Icon(destination.icon, contentDescription = null) }, label = { Text(destination.label) }, selected = currentDestination?.hierarchy?.any { it.route == destination.route } == true, onClick = { navController.navigate(destination.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } ) } } } ) { innerPadding -> NavHost( navController = navController, startDestination = NavigationDestination.Home.route, modifier = Modifier.padding(innerPadding) ) { composable(NavigationDestination.Home.route) { HomeScreen() } composable(NavigationDestination.Search.route) { SearchScreen() } composable(NavigationDestination.Profile.route) { ProfileScreen() } } } } ``` **Navigation Drawer:** ```kotlin @Composable fun DrawerNavigation() { val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet { Spacer(Modifier.height(12.dp)) Text( "App Name", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleLarge ) HorizontalDivider() NavigationDrawerItem( icon = { Icon(Icons.Default.Home, null) }, label = { Text("Home") }, selected = true, onClick = { scope.launch { drawerState.close() } } ) NavigationDrawerItem( icon = { Icon(Icons.Default.Settings, null) }, label = { Text("Settings") }, selected = false, onClick = { } ) } } ) { Scaffold( topBar = { TopAppBar( title = { Text("Home") }, navigationIcon = { IconButton(onClick = { scope.launch { drawerState.open() } }) { Icon(Icons.Default.Menu, contentDescription = "Menu") } } ) } ) { innerPadding -> Content(modifier = Modifier.padding(innerPadding)) } } } ``` ### 4. Material 3 Theming **Color Scheme:** ```kotlin // Dynamic color (Android 12+) val dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } else { if (darkTheme) DarkColorScheme else LightColorScheme } // Custom color scheme private val LightColorScheme = lightColorScheme( primary = Color(0xFF6750A4), onPrimary = Color.White, primaryContainer = Color(0xFFEADDFF), onPrimaryContainer = Color(0xFF21005D), secondary = Color(0xFF625B71), onSecondary = Color.White, tertiary = Color(0xFF7D5260), onTertiary = Color.White, surface = Color(0xFFFFFBFE), onSurface = Color(0xFF1C1B1F), ) ``` **Typography:** ```kotlin val AppTypography = Typography( displayLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 57.sp, lineHeight = 64.sp ), headlineMedium = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 28.sp, lineHeight = 36.sp ), titleLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 22.sp, lineHeight = 28.sp ), bodyLarge = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp ), labelMedium = TextStyle( fontFamily = FontFamily.Default, fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp ) ) ``` ### 5. Component Examples **Cards:** ```kotlin @Composable fun FeatureCard( title: String, description: String, imageUrl: String, onClick: () -> Unit ) { Card( onClick = onClick, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceVariant ) ) { Column { AsyncImage( model = imageUrl, contentDescription = null, modifier = Modifier .fillMaxWidth() .height(180.dp), contentScale = ContentScale.Crop ) Column(modifier = Modifier.padding(16.dp)) { Text( text = title, style = MaterialTheme.typography.titleMedium ) Spacer(modifier = Modifier.height(8.dp)) Text( text = description, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } ``` **Buttons:** ```kotlin // Filled button (primary action) Button(onClick = { }) { Text("Continue") } // Filled tonal button (secondary action) FilledTonalButton(onClick = { }) { Icon(Icons.Default.Add, null) Spacer(Modifier.width(8.dp)) Text("Add Item") } // Outlined button OutlinedButton(onClick = { }) { Text("Cancel") } // Text button TextButton(onClick = { }) { Text("Learn More") } // FAB FloatingActionButton( onClick = { }, containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer ) { Icon(Icons.Default.Add, contentDescription = "Add") } ``` ## Quick Start Component ```kotlin @Composable fun ItemListCard( item: Item, onItemClick: () -> Unit, modifier: Modifier = Modifier ) { Card( onClick = onItemClick, modifier = modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier .padding(16.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(48.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.primaryContainer), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Default.Star, contentDescription = null, tint = MaterialTheme.colorScheme.onPrimaryContainer ) } Spacer(modifier = Modifier.width(16.dp)) Column(modifier = Modifier.weight(1f)) { Text( text = item.title, style = MaterialTheme.typography.titleMedium ) Text( text = item.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } Icon( imageVector = Icons.Default.ChevronRight, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant ) } } } ``` ## Best Practices 1. **Use Material Theme**: Access colors via `MaterialTheme.colorScheme` for automatic dark mode support 2. **Support Dynamic Color**: Enable dynamic color on Android 12+ for personalization 3. **Adaptive Layouts**: Use `WindowSizeClass` for responsive designs 4. **Content Descriptions**: Add `contentDescription` to all interactive elements 5. **Touch Targets**: Minimum 48dp touch targets for accessibility 6. **State Hoisting**: Hoist state to make components reusable and testable 7. **Remember Properly**: Use `remember` and `rememberSaveable` appropriately 8. **Preview Annotations**: Add `@Preview` with different configurations ## Common Issues - **Recomposition Issues**: Avoid passing unstable lambdas; use `remember` - **State Loss**: Use `rememberSaveable` for configuration changes - **Performance**: Use `LazyColumn` instead of `Column` for long lists - **Theme Leaks**: Ensure `MaterialTheme` wraps all composables - **Navigation Crashes**: Handle back press and deep links properly - **Memory Leaks**: Cancel coroutines in `DisposableEffect` ## Resources - [Material Design 3](https://m3.material.io/) - [Jetpack Compose Documentation](https://developer.android.com/jetpack/compose) - [Compose Samples](https://github.com/android/compose-samples) - [Material 3 Compose](https://developer.android.com/jetpack/compose/designsystems/material3)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

mobile-ios-design

Master iOS Human Interface Guidelines and SwiftUI patterns for

coding
⭐1
# iOS Mobile Design Master iOS Human Interface Guidelines (HIG) and SwiftUI patterns to build polished, native iOS applications that feel at home on Apple platforms. ## When to Use This Skill - Designing iOS app interfaces following Apple HIG - Building SwiftUI views and layouts - Implementing iOS navigation patterns (NavigationStack, TabView, sheets) - Creating adaptive layouts for iPhone and iPad - Using SF Symbols and system typography - Building accessible iOS interfaces - Implementing iOS-specific gestures and interactions - Designing for Dynamic Type and Dark Mode ## Core Concepts ### 1. Human Interface Guidelines Principles **Clarity**: Content is legible, icons are precise, adornments are subtle **Deference**: UI helps users understand content without competing with it **Depth**: Visual layers and motion convey hierarchy and enable navigation **Platform Considerations:** - **iOS**: Touch-first, compact displays, portrait orientation - **iPadOS**: Larger canvas, multitasking, pointer support - **visionOS**: Spatial computing, eye/hand input ### 2. SwiftUI Layout System **Stack-Based Layouts:** ```swift // Vertical stack with alignment VStack(alignment: .leading, spacing: 12) { Text("Title") .font(.headline) Text("Subtitle") .font(.subheadline) .foregroundStyle(.secondary) } // Horizontal stack with flexible spacing HStack { Image(systemName: "star.fill") Text("Featured") Spacer() Text("View All") .foregroundStyle(.blue) } ``` **Grid Layouts:** ```swift // Adaptive grid that fills available width LazyVGrid(columns: [ GridItem(.adaptive(minimum: 150, maximum: 200)) ], spacing: 16) { ForEach(items) { item in ItemCard(item: item) } } // Fixed column grid LazyVGrid(columns: [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ], spacing: 12) { ForEach(items) { item in ItemThumbnail(item: item) } } ``` ### 3. Navigation Patterns **NavigationStack (iOS 16+):** ```swift struct ContentView: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { List(items) { item in NavigationLink(value: item) { ItemRow(item: item) } } .navigationTitle("Items") .navigationDestination(for: Item.self) { item in ItemDetailView(item: item) } } } } ``` **TabView (iOS 18+):** ```swift struct MainTabView: View { @State private var selectedTab = 0 var body: some View { TabView(selection: $selectedTab) { Tab("Home", systemImage: "house", value: 0) { HomeView() } Tab("Search", systemImage: "magnifyingglass", value: 1) { SearchView() } Tab("Profile", systemImage: "person", value: 2) { ProfileView() } } } } ``` ### 4. System Integration **SF Symbols:** ```swift // Basic symbol Image(systemName: "heart.fill") .foregroundStyle(.red) // Symbol with rendering mode Image(systemName: "cloud.sun.fill") .symbolRenderingMode(.multicolor) // Variable symbol (iOS 16+) Image(systemName: "speaker.wave.3.fill", variableValue: volume) // Symbol effect (iOS 17+) Image(systemName: "bell.fill") .symbolEffect(.bounce, value: notificationCount) ``` **Dynamic Type:** ```swift // Use semantic fonts Text("Headline") .font(.headline) Text("Body text that scales with user preferences") .font(.body) // Custom font that respects Dynamic Type Text("Custom") .font(.custom("Avenir", size: 17, relativeTo: .body)) ``` ### 5. Visual Design **Colors and Materials:** ```swift // Semantic colors that adapt to light/dark mode Text("Primary") .foregroundStyle(.primary) Text("Secondary") .foregroundStyle(.secondary) // System materials for blur effects Rectangle() .fill(.ultraThinMaterial) .frame(height: 100) // Vibrant materials for overlays Text("Overlay") .padding() .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) ``` **Shadows and Depth:** ```swift // Standard card shadow RoundedRectangle(cornerRadius: 16) .fill(.background) .shadow(color: .black.opacity(0.1), radius: 8, y: 4) // Elevated appearance .shadow(radius: 2, y: 1) .shadow(radius: 8, y: 4) ``` ## Quick Start Component ```swift import SwiftUI struct FeatureCard: View { let title: String let description: String let systemImage: String var body: some View { HStack(spacing: 16) { Image(systemName: systemImage) .font(.title) .foregroundStyle(.blue) .frame(width: 44, height: 44) .background(.blue.opacity(0.1), in: Circle()) VStack(alignment: .leading, spacing: 4) { Text(title) .font(.headline) Text(description) .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(2) } Spacer() Image(systemName: "chevron.right") .foregroundStyle(.tertiary) } .padding() .background(.background, in: RoundedRectangle(cornerRadius: 12)) .shadow(color: .black.opacity(0.05), radius: 4, y: 2) } } ``` ## Best Practices 1. **Use Semantic Colors**: Always use `.primary`, `.secondary`, `.background` for automatic light/dark mode support 2. **Embrace SF Symbols**: Use system symbols for consistency and automatic accessibility 3. **Support Dynamic Type**: Use semantic fonts (`.body`, `.headline`) instead of fixed sizes 4. **Add Accessibility**: Include `.accessibilityLabel()` and `.accessibilityHint()` modifiers 5. **Use Safe Areas**: Respect `safeAreaInset` and avoid hardcoded padding at screen edges 6. **Implement State Restoration**: Use `@SceneStorage` for preserving user state 7. **Support iPad Multitasking**: Design for split view and slide over 8. **Test on Device**: Simulator doesn't capture full haptic and performance experience ## Common Issues - **Layout Breaking**: Use `.fixedSize()` sparingly; prefer flexible layouts - **Performance Issues**: Use `LazyVStack`/`LazyHStack` for long scrolling lists - **Navigation Bugs**: Ensure `NavigationLink` values are `Hashable` - **Dark Mode Problems**: Avoid hardcoded colors; use semantic or asset catalog colors - **Accessibility Failures**: Test with VoiceOver enabled - **Memory Leaks**: Watch for strong reference cycles in closures ## Resources - [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) - [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui) - [SF Symbols App](https://developer.apple.com/sf-symbols/) - [WWDC SwiftUI Sessions](https://developer.apple.com/videos/swiftui/)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

react-native-design

Master React Native styling, navigation, and Reanimated animations

coding
⭐1
# React Native Design Master React Native styling patterns, React Navigation, and Reanimated 3 to build performant, cross-platform mobile applications with native-quality user experiences. ## When to Use This Skill - Building cross-platform mobile apps with React Native - Implementing navigation with React Navigation 6+ - Creating performant animations with Reanimated 3 - Styling components with StyleSheet and styled-components - Building responsive layouts for different screen sizes - Implementing platform-specific designs (iOS/Android) - Creating gesture-driven interactions with Gesture Handler - Optimizing React Native performance ## Core Concepts ### 1. StyleSheet and Styling **Basic StyleSheet:** ```typescript import { StyleSheet, View, Text } from 'react-native'; const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: '#ffffff', }, title: { fontSize: 24, fontWeight: '600', color: '#1a1a1a', marginBottom: 8, }, subtitle: { fontSize: 16, color: '#666666', lineHeight: 24, }, }); function Card() { return ( <View style={styles.container}> <Text style={styles.title}>Title</Text> <Text style={styles.subtitle}>Subtitle text</Text> </View> ); } ``` **Dynamic Styles:** ```typescript interface CardProps { variant: 'primary' | 'secondary'; disabled?: boolean; } function Card({ variant, disabled }: CardProps) { return ( <View style={[ styles.card, variant === 'primary' ? styles.primary : styles.secondary, disabled && styles.disabled, ]}> <Text style={styles.text}>Content</Text> </View> ); } const styles = StyleSheet.create({ card: { padding: 16, borderRadius: 12, }, primary: { backgroundColor: '#6366f1', }, secondary: { backgroundColor: '#f3f4f6', borderWidth: 1, borderColor: '#e5e7eb', }, disabled: { opacity: 0.5, }, text: { fontSize: 16, }, }); ``` ### 2. Flexbox Layout **Row and Column Layouts:** ```typescript const styles = StyleSheet.create({ // Vertical stack (column) column: { flexDirection: "column", gap: 12, }, // Horizontal stack (row) row: { flexDirection: "row", alignItems: "center", gap: 8, }, // Space between items spaceBetween: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", }, // Centered content centered: { flex: 1, justifyContent: "center", alignItems: "center", }, // Fill remaining space fill: { flex: 1, }, }); ``` ### 3. React Navigation Setup **Stack Navigator:** ```typescript import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; type RootStackParamList = { Home: undefined; Detail: { itemId: string }; Settings: undefined; }; const Stack = createNativeStackNavigator<RootStackParamList>(); function AppNavigator() { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home" screenOptions={{ headerStyle: { backgroundColor: '#6366f1' }, headerTintColor: '#ffffff', headerTitleStyle: { fontWeight: '600' }, }} > <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Home' }} /> <Stack.Screen name="Detail" component={DetailScreen} options={({ route }) => ({ title: `Item ${route.params.itemId}`, })} /> <Stack.Screen name="Settings" component={SettingsScreen} /> </Stack.Navigator> </NavigationContainer> ); } ``` **Tab Navigator:** ```typescript import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Ionicons } from '@expo/vector-icons'; type TabParamList = { Home: undefined; Search: undefined; Profile: undefined; }; const Tab = createBottomTabNavigator<TabParamList>(); function TabNavigator() { return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ focused, color, size }) => { const icons: Record<string, keyof typeof Ionicons.glyphMap> = { Home: focused ? 'home' : 'home-outline', Search: focused ? 'search' : 'search-outline', Profile: focused ? 'person' : 'person-outline', }; return <Ionicons name={icons[route.name]} size={size} color={color} />; }, tabBarActiveTintColor: '#6366f1', tabBarInactiveTintColor: '#9ca3af', })} > <Tab.Screen name="Home" component={HomeScreen} /> <Tab.Screen name="Search" component={SearchScreen} /> <Tab.Screen name="Profile" component={ProfileScreen} /> </Tab.Navigator> ); } ``` ### 4. Reanimated 3 Basics **Animated Values:** ```typescript import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, } from 'react-native-reanimated'; function AnimatedBox() { const scale = useSharedValue(1); const opacity = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], opacity: opacity.value, })); const handlePress = () => { scale.value = withSpring(1.2, {}, () => { scale.value = withSpring(1); }); }; return ( <Pressable onPress={handlePress}> <Animated.View style={[styles.box, animatedStyle]} /> </Pressable> ); } ``` **Gesture Handler Integration:** ```typescript import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated'; function DraggableCard() { const translateX = useSharedValue(0); const translateY = useSharedValue(0); const gesture = Gesture.Pan() .onUpdate((event) => { translateX.value = event.translationX; translateY.value = event.translationY; }) .onEnd(() => { translateX.value = withSpring(0); translateY.value = withSpring(0); }); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], })); return ( <GestureDetector gesture={gesture}> <Animated.View style={[styles.card, animatedStyle]}> <Text>Drag me!</Text> </Animated.View> </GestureDetector> ); } ``` ### 5. Platform-Specific Styling ```typescript import { Platform, StyleSheet } from "react-native"; const styles = StyleSheet.create({ container: { padding: 16, ...Platform.select({ ios: { shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, }, android: { elevation: 4, }, }), }, text: { fontFamily: Platform.OS === "ios" ? "SF Pro Text" : "Roboto", fontSize: 16, }, }); // Platform-specific components import { Platform } from "react-native"; const StatusBarHeight = Platform.OS === "ios" ? 44 : 0; ``` ## Quick Start Component ```typescript import React from 'react'; import { View, Text, StyleSheet, Pressable, Image, } from 'react-native'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated'; interface ItemCardProps { title: string; subtitle: string; imageUrl: string; onPress: () => void; } const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export function ItemCard({ title, subtitle, imageUrl, onPress }: ItemCardProps) { const scale = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); return ( <AnimatedPressable style={[styles.card, animatedStyle]} onPress={onPress} onPressIn={() => { scale.value = withSpring(0.97); }} onPressOut={() => { scale.value = withSpring(1); }} > <Image source={{ uri: imageUrl }} style={styles.image} /> <View style={styles.content}> <Text style={styles.title} numberOfLines={1}> {title} </Text> <Text style={styles.subtitle} numberOfLines={2}> {subtitle} </Text> </View> </AnimatedPressable> ); } const styles = StyleSheet.create({ card: { backgroundColor: '#ffffff', borderRadius: 16, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 4, }, image: { width: '100%', height: 160, backgroundColor: '#f3f4f6', }, content: { padding: 16, gap: 4, }, title: { fontSize: 18, fontWeight: '600', color: '#1f2937', }, subtitle: { fontSize: 14, color: '#6b7280', lineHeight: 20, }, }); ``` ## Best Practices 1. **Use TypeScript**: Define navigation and prop types for type safety 2. **Memoize Components**: Use `React.memo` and `useCallback` to prevent unnecessary rerenders 3. **Run Animations on UI Thread**: Use Reanimated worklets for 60fps animations 4. **Avoid Inline Styles**: Use StyleSheet.create for performance 5. **Handle Safe Areas**: Use `SafeAreaView` or `useSafeAreaInsets` 6. **Test on Real Devices**: Simulator/emulator performance differs from real devices 7. **Use FlatList for Lists**: Never use ScrollView with map for long lists 8. **Platform-Specific Code**: Use Platform.select for iOS/Android differences ## Common Issues - **Gesture Conflicts**: Wrap gestures with `GestureDetector` and use `simultaneousHandlers` - **Navigation Type Errors**: Define `ParamList` types for all navigators - **Animation Jank**: Move animations to UI thread with `runOnUI` worklets - **Memory Leaks**: Cancel animations and cleanup in useEffect - **Font Loading**: Use `expo-font` or `react-native-asset` for custom fonts - **Safe Area Issues**: Test on notched devices (iPhone, Android with cutouts) ## Resources - [React Native Documentation](https://reactnative.dev/) - [React Navigation](https://reactnavigation.org/) - [Reanimated Documentation](https://docs.swmansion.com/react-native-reanimated/) - [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/) - [Expo Documentation](https://docs.expo.dev/)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

responsive-design

Implement modern responsive layouts using container queries, fluid

coding
⭐1
# Responsive Design Master modern responsive design techniques to create interfaces that adapt seamlessly across all screen sizes and device contexts. ## When to Use This Skill - Implementing mobile-first responsive layouts - Using container queries for component-based responsiveness - Creating fluid typography and spacing scales - Building complex layouts with CSS Grid and Flexbox - Designing breakpoint strategies for design systems - Implementing responsive images and media - Creating adaptive navigation patterns - Building responsive tables and data displays ## Core Capabilities ### 1. Container Queries - Component-level responsiveness independent of viewport - Container query units (cqi, cqw, cqh) - Style queries for conditional styling - Fallbacks for browser support ### 2. Fluid Typography & Spacing - CSS clamp() for fluid scaling - Viewport-relative units (vw, vh, dvh) - Fluid type scales with min/max bounds - Responsive spacing systems ### 3. Layout Patterns - CSS Grid for 2D layouts - Flexbox for 1D distribution - Intrinsic layouts (content-based sizing) - Subgrid for nested grid alignment ### 4. Breakpoint Strategy - Mobile-first media queries - Content-based breakpoints - Design token integration - Feature queries (@supports) ## Quick Reference ### Modern Breakpoint Scale ```css /* Mobile-first breakpoints */ /* Base: Mobile (< 640px) */ @media (min-width: 640px) { /* sm: Landscape phones, small tablets */ } @media (min-width: 768px) { /* md: Tablets */ } @media (min-width: 1024px) { /* lg: Laptops, small desktops */ } @media (min-width: 1280px) { /* xl: Desktops */ } @media (min-width: 1536px) { /* 2xl: Large desktops */ } /* Tailwind CSS equivalent */ /* sm: @media (min-width: 640px) */ /* md: @media (min-width: 768px) */ /* lg: @media (min-width: 1024px) */ /* xl: @media (min-width: 1280px) */ /* 2xl: @media (min-width: 1536px) */ ``` ## Key Patterns ### Pattern 1: Container Queries ```css /* Define a containment context */ .card-container { container-type: inline-size; container-name: card; } /* Query the container, not the viewport */ @container card (min-width: 400px) { .card { display: grid; grid-template-columns: 200px 1fr; gap: 1rem; } .card-image { aspect-ratio: 1; } } @container card (min-width: 600px) { .card { grid-template-columns: 250px 1fr; } .card-title { font-size: 1.5rem; } } /* Container query units */ .card-title { /* 5% of container width, clamped between 1rem and 2rem */ font-size: clamp(1rem, 5cqi, 2rem); } ``` ```tsx // React component with container queries function ResponsiveCard({ title, image, description }) { return ( <div className="@container"> <article className="flex flex-col @md:flex-row @md:gap-4"> <img src={image} alt="" className="w-full @md:w-48 @lg:w-64 aspect-video @md:aspect-square object-cover" /> <div className="p-4 @md:p-0"> <h2 className="text-lg @md:text-xl @lg:text-2xl font-semibold"> {title} </h2> <p className="mt-2 text-muted-foreground @md:line-clamp-3"> {description} </p> </div> </article> </div> ); } ``` ### Pattern 2: Fluid Typography ```css /* Fluid type scale using clamp() */ :root { /* Min size, preferred (fluid), max size */ --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); --text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem); --text-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem); --text-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem); --text-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem); } /* Usage */ h1 { font-size: var(--text-4xl); } h2 { font-size: var(--text-3xl); } h3 { font-size: var(--text-2xl); } p { font-size: var(--text-base); } /* Fluid spacing scale */ :root { --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem); --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem); } ``` ```tsx // Utility function for fluid values function fluidValue( minSize: number, maxSize: number, minWidth = 320, maxWidth = 1280, ) { const slope = (maxSize - minSize) / (maxWidth - minWidth); const yAxisIntersection = -minWidth * slope + minSize; return `clamp(${minSize}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxSize}rem)`; } // Generate fluid type scale const fluidTypeScale = { sm: fluidValue(0.875, 1), base: fluidValue(1, 1.125), lg: fluidValue(1.25, 1.5), xl: fluidValue(1.5, 2), "2xl": fluidValue(2, 3), }; ``` ### Pattern 3: CSS Grid Responsive Layout ```css /* Auto-fit grid - items wrap automatically */ .grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 1.5rem; } /* Auto-fill grid - maintains empty columns */ .grid-auto-fill { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem; } /* Responsive grid with named areas */ .page-layout { display: grid; grid-template-areas: "header" "main" "sidebar" "footer"; gap: 1rem; } @media (min-width: 768px) { .page-layout { grid-template-columns: 1fr 300px; grid-template-areas: "header header" "main sidebar" "footer footer"; } } @media (min-width: 1024px) { .page-layout { grid-template-columns: 250px 1fr 300px; grid-template-areas: "header header header" "nav main sidebar" "footer footer footer"; } } .header { grid-area: header; } .main { grid-area: main; } .sidebar { grid-area: sidebar; } .footer { grid-area: footer; } ``` ```tsx // Responsive grid component function ResponsiveGrid({ children, minItemWidth = "250px", gap = "1.5rem" }) { return ( <div className="grid" style={{ gridTemplateColumns: `repeat(auto-fit, minmax(min(${minItemWidth}, 100%), 1fr))`, gap, }} > {children} </div> ); } // Usage with Tailwind function ProductGrid({ products }) { return ( <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6"> {products.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </div> ); } ``` ### Pattern 4: Responsive Navigation ```tsx function ResponsiveNav({ items }) { const [isOpen, setIsOpen] = useState(false); return ( <nav className="relative"> {/* Mobile menu button */} <button className="lg:hidden p-2" onClick={() => setIsOpen(!isOpen)} aria-expanded={isOpen} aria-controls="nav-menu" > <span className="sr-only">Toggle navigation</span> {isOpen ? <X /> : <Menu />} </button> {/* Navigation links */} <ul id="nav-menu" className={cn( // Base: hidden on mobile "absolute top-full left-0 right-0 bg-background border-b", "flex flex-col", // Mobile: slide down isOpen ? "flex" : "hidden", // Desktop: always visible, horizontal "lg:static lg:flex lg:flex-row lg:border-0 lg:bg-transparent", )} > {items.map((item) => ( <li key={item.href}> <a href={item.href} className={cn( "block px-4 py-3", "lg:px-3 lg:py-2", "hover:bg-muted lg:hover:bg-transparent lg:hover:text-primary", )} > {item.label} </a> </li> ))} </ul> </nav> ); } ``` ### Pattern 5: Responsive Images ```tsx // Responsive image with art direction function ResponsiveHero() { return ( <picture> {/* Art direction: different crops for different screens */} <source media="(min-width: 1024px)" srcSet="/hero-wide.webp" type="image/webp" /> <source media="(min-width: 768px)" srcSet="/hero-medium.webp" type="image/webp" /> <source srcSet="/hero-mobile.webp" type="image/webp" /> {/* Fallback */} <img src="/hero-mobile.jpg" alt="Hero image description" className="w-full h-auto" loading="eager" fetchpriority="high" /> </picture> ); } // Responsive image with srcset for resolution switching function ProductImage({ product }) { return ( <img src={product.image} srcSet={` ${product.image}?w=400 400w, ${product.image}?w=800 800w, ${product.image}?w=1200 1200w `} sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" alt={product.name} className="w-full h-auto object-cover" loading="lazy" /> ); } ``` ### Pattern 6: Responsive Tables ```tsx // Responsive table with horizontal scroll function ResponsiveTable({ data, columns }) { return ( <div className="w-full overflow-x-auto"> <table className="w-full min-w-[600px]"> <thead> <tr> {columns.map((col) => ( <th key={col.key} className="text-left p-3"> {col.label} </th> ))} </tr> </thead> <tbody> {data.map((row, i) => ( <tr key={i} className="border-t"> {columns.map((col) => ( <td key={col.key} className="p-3"> {row[col.key]} </td> ))} </tr> ))} </tbody> </table> </div> ); } // Card-based table for mobile function ResponsiveDataTable({ data, columns }) { return ( <> {/* Desktop table */} <table className="hidden md:table w-full"> {/* ... standard table */} </table> {/* Mobile cards */} <div className="md:hidden space-y-4"> {data.map((row, i) => ( <div key={i} className="border rounded-lg p-4 space-y-2"> {columns.map((col) => ( <div key={col.key} className="flex justify-between"> <span className="font-medium text-muted-foreground"> {col.label} </span> <span>{row[col.key]}</span> </div> ))} </div> ))} </div> </> ); } ``` ## Viewport Units ```css /* Standard viewport units */ .full-height { height: 100vh; /* May cause issues on mobile */ } /* Dynamic viewport units (recommended for mobile) */ .full-height-dynamic { height: 100dvh; /* Accounts for mobile browser UI */ } /* Small viewport (minimum) */ .min-full-height { min-height: 100svh; } /* Large viewport (maximum) */ .max-full-height { max-height: 100lvh; } /* Viewport-relative font sizing */ .hero-title { /* 5vw with min/max bounds */ font-size: clamp(2rem, 5vw, 4rem); } ``` ## Best Practices 1. **Mobile-First**: Start with mobile styles, enhance for larger screens 2. **Content Breakpoints**: Set breakpoints based on content, not devices 3. **Fluid Over Fixed**: Use fluid values for typography and spacing 4. **Container Queries**: Use for component-level responsiveness 5. **Test Real Devices**: Simulators don't catch all issues 6. **Performance**: Optimize images, lazy load off-screen content 7. **Touch Targets**: Maintain 44x44px minimum on mobile 8. **Logical Properties**: Use inline/block for internationalization ## Common Issues - **Horizontal Overflow**: Content breaking out of viewport - **Fixed Widths**: Using px instead of relative units - **Viewport Height**: 100vh issues on mobile browsers - **Font Size**: Text too small on mobile - **Touch Targets**: Buttons too small to tap accurately - **Aspect Ratio**: Images squishing or stretching - **Z-Index Stacking**: Overlays breaking on different screens ## Resources - [CSS Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) - [Utopia Fluid Type Calculator](https://utopia.fyi/type/calculator/) - [Every Layout](https://every-layout.dev/) - [Responsive Images Guide](https://web.dev/responsive-images/) - [CSS Grid Garden](https://cssgridgarden.com/)
👍0
👁️0
🤖 Auto-discovered
🤖system prompt•7 months ago

visual-design-foundations

Apply typography, color theory, spacing systems, and iconography

coding
⭐1
# Visual Design Foundations Build cohesive, accessible visual systems using typography, color, spacing, and iconography fundamentals. ## When to Use This Skill - Establishing design tokens for a new project - Creating or refining a spacing and sizing system - Selecting and pairing typefaces - Building accessible color palettes - Designing icon systems and visual assets - Improving visual hierarchy and readability - Auditing designs for visual consistency - Implementing dark mode or theming ## Core Systems ### 1. Typography Scale **Modular Scale** (ratio-based sizing): ```css :root { --font-size-xs: 0.75rem; /* 12px */ --font-size-sm: 0.875rem; /* 14px */ --font-size-base: 1rem; /* 16px */ --font-size-lg: 1.125rem; /* 18px */ --font-size-xl: 1.25rem; /* 20px */ --font-size-2xl: 1.5rem; /* 24px */ --font-size-3xl: 1.875rem; /* 30px */ --font-size-4xl: 2.25rem; /* 36px */ --font-size-5xl: 3rem; /* 48px */ } ``` **Line Height Guidelines**: | Text Type | Line Height | |-----------|-------------| | Headings | 1.1 - 1.3 | | Body text | 1.5 - 1.7 | | UI labels | 1.2 - 1.4 | ### 2. Spacing System **8-point grid** (industry standard): ```css :root { --space-1: 0.25rem; /* 4px */ --space-2: 0.5rem; /* 8px */ --space-3: 0.75rem; /* 12px */ --space-4: 1rem; /* 16px */ --space-5: 1.25rem; /* 20px */ --space-6: 1.5rem; /* 24px */ --space-8: 2rem; /* 32px */ --space-10: 2.5rem; /* 40px */ --space-12: 3rem; /* 48px */ --space-16: 4rem; /* 64px */ } ``` ### 3. Color System **Semantic color tokens**: ```css :root { /* Brand */ --color-primary: #2563eb; --color-primary-hover: #1d4ed8; --color-primary-active: #1e40af; /* Semantic */ --color-success: #16a34a; --color-warning: #ca8a04; --color-error: #dc2626; --color-info: #0891b2; /* Neutral */ --color-gray-50: #f9fafb; --color-gray-100: #f3f4f6; --color-gray-200: #e5e7eb; --color-gray-300: #d1d5db; --color-gray-400: #9ca3af; --color-gray-500: #6b7280; --color-gray-600: #4b5563; --color-gray-700: #374151; --color-gray-800: #1f2937; --color-gray-900: #111827; } ``` ## Quick Start: Design Tokens in Tailwind ```js // tailwind.config.js module.exports = { theme: { extend: { fontFamily: { sans: ["Inter", "system-ui", "sans-serif"], mono: ["JetBrains Mono", "monospace"], }, fontSize: { xs: ["0.75rem", { lineHeight: "1rem" }], sm: ["0.875rem", { lineHeight: "1.25rem" }], base: ["1rem", { lineHeight: "1.5rem" }], lg: ["1.125rem", { lineHeight: "1.75rem" }], xl: ["1.25rem", { lineHeight: "1.75rem" }], "2xl": ["1.5rem", { lineHeight: "2rem" }], }, colors: { brand: { 50: "#eff6ff", 500: "#3b82f6", 600: "#2563eb", 700: "#1d4ed8", }, }, spacing: { // Extends default with custom values 18: "4.5rem", 88: "22rem", }, }, }, }; ``` ## Typography Best Practices ### Font Pairing **Safe combinations**: - Heading: **Inter** / Body: **Inter** (single family) - Heading: **Playfair Display** / Body: **Source Sans Pro** (contrast) - Heading: **Space Grotesk** / Body: **IBM Plex Sans** (geometric) ### Responsive Typography ```css /* Fluid typography using clamp() */ h1 { font-size: clamp(2rem, 5vw + 1rem, 3.5rem); line-height: 1.1; } p { font-size: clamp(1rem, 2vw + 0.5rem, 1.125rem); line-height: 1.6; max-width: 65ch; /* Optimal reading width */ } ``` ### Font Loading ```css /* Prevent layout shift */ @font-face { font-family: "Inter"; src: url("/fonts/Inter.woff2") format("woff2"); font-display: swap; font-weight: 400 700; } ``` ## Color Theory ### Contrast Requirements (WCAG) | Element | Minimum Ratio | | ------------------ | ------------- | | Body text | 4.5:1 (AA) | | Large text (18px+) | 3:1 (AA) | | UI components | 3:1 (AA) | | Enhanced | 7:1 (AAA) | ### Dark Mode Strategy ```css :root { --bg-primary: #ffffff; --bg-secondary: #f9fafb; --text-primary: #111827; --text-secondary: #6b7280; --border: #e5e7eb; } [data-theme="dark"] { --bg-primary: #111827; --bg-secondary: #1f2937; --text-primary: #f9fafb; --text-secondary: #9ca3af; --border: #374151; } ``` ### Color Accessibility ```tsx // Check contrast programmatically function getContrastRatio(foreground: string, background: string): number { const getLuminance = (hex: string) => { const rgb = hexToRgb(hex); const [r, g, b] = rgb.map((c) => { c = c / 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const l1 = getLuminance(foreground); const l2 = getLuminance(background); const lighter = Math.max(l1, l2); const darker = Math.min(l1, l2); return (lighter + 0.05) / (darker + 0.05); } ``` ## Spacing Guidelines ### Component Spacing ``` Card padding: 16-24px (--space-4 to --space-6) Section gap: 32-64px (--space-8 to --space-16) Form field gap: 16-24px (--space-4 to --space-6) Button padding: 8-16px vertical, 16-24px horizontal Icon-text gap: 8px (--space-2) ``` ### Visual Rhythm ```css /* Consistent vertical rhythm */ .prose > * + * { margin-top: var(--space-4); } .prose > h2 + * { margin-top: var(--space-2); } .prose > * + h2 { margin-top: var(--space-8); } ``` ## Iconography ### Icon Sizing System ```css :root { --icon-xs: 12px; --icon-sm: 16px; --icon-md: 20px; --icon-lg: 24px; --icon-xl: 32px; } ``` ### Icon Component ```tsx interface IconProps { name: string; size?: "xs" | "sm" | "md" | "lg" | "xl"; className?: string; } const sizeMap = { xs: 12, sm: 16, md: 20, lg: 24, xl: 32, }; export function Icon({ name, size = "md", className }: IconProps) { return ( <svg width={sizeMap[size]} height={sizeMap[size]} className={cn("inline-block flex-shrink-0", className)} aria-hidden="true" > <use href={`/icons.svg#${name}`} /> </svg> ); } ``` ## Best Practices 1. **Establish Constraints**: Limit choices to maintain consistency 2. **Document Decisions**: Create a living style guide 3. **Test Accessibility**: Verify contrast, sizing, touch targets 4. **Use Semantic Tokens**: Name by purpose, not appearance 5. **Design Mobile-First**: Start with constraints, add complexity 6. **Maintain Vertical Rhythm**: Consistent spacing creates harmony 7. **Limit Font Weights**: 2-3 weights per family is sufficient ## Common Issues - **Inconsistent Spacing**: Not using a defined scale - **Poor Contrast**: Failing WCAG requirements - **Font Overload**: Too many families or weights - **Magic Numbers**: Arbitrary values instead of tokens - **Missing States**: Forgetting hover, focus, disabled - **No Dark Mode Plan**: Retrofitting is harder than planning ## Resources - [Tailwind CSS Documentation](https://tailwindcss.com/docs) - [Type Scale Calculator](https://typescale.com/) - [Contrast Checker](https://webaim.org/resources/contrastchecker/) - [Material Design Color System](https://m3.material.io/styles/color/overview) - [Radix Colors](https://www.radix-ui.com/colors)
👍0
👁️0
🤖 Auto-discovered