🎯

mobile-components

🎯Skill

from dadbodgeoff/drift

VibeIndex|
What it does

Provides touch-optimized mobile UI components like bottom navigation, bottom sheets, and swipe actions for responsive web apps.

πŸ“¦

Part of

dadbodgeoff/drift(69 items)

mobile-components

Installation

npm installInstall npm package
npm install -g driftdetect
npm installInstall npm package
npm install -g driftdetect@latest
npm installInstall npm package
npm install -g driftdetect-mcp
Claude Desktop ConfigurationAdd this to your claude_desktop_config.json
{ "mcpServers": { "drift": { "command": "driftdetect-mcp" } } ...
πŸ“– Extracted from docs: dadbodgeoff/drift
4Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Mobile-first UI components including bottom navigation, bottom sheets, pull-to-refresh, and swipe actions. Touch-optimized with proper gesture handling.

Overview

# Mobile Components

Touch-optimized UI components for mobile-first experiences.

When to Use This Skill

  • Building mobile-first or responsive web applications
  • Need native-feeling mobile interactions
  • Implementing bottom sheets, pull-to-refresh, or swipe actions
  • Desktop components feel awkward on mobile

Core Concepts

Mobile UX differs from desktop: bottom navigation is reachable, sheets slide up from bottom, touch targets need 44px minimum, and gestures replace clicks.

Implementation

TypeScript/React

```typescript

// Bottom Navigation

interface NavItem {

href: string;

label: string;

icon: string;

}

function MobileNav({ items }: { items: NavItem[] }) {

const pathname = usePathname();

return (

);

}

// Bottom Sheet

interface BottomSheetProps {

isOpen: boolean;

onClose: () => void;

children: ReactNode;

title?: string;

}

function BottomSheet({ isOpen, onClose, children, title }: BottomSheetProps) {

const [dragY, setDragY] = useState(0);

const [isDragging, setIsDragging] = useState(false);

const startY = useRef(0);

useEffect(() => {

document.body.style.overflow = isOpen ? 'hidden' : '';

return () => { document.body.style.overflow = ''; };

}, [isOpen]);

const handleTouchStart = (e: React.TouchEvent) => {

startY.current = e.touches[0].clientY;

setIsDragging(true);

};

const handleTouchMove = (e: React.TouchEvent) => {

if (!isDragging) return;

const diff = e.touches[0].clientY - startY.current;

if (diff > 0) setDragY(diff);

};

const handleTouchEnd = () => {

setIsDragging(false);

if (dragY > 100) onClose();

setDragY(0);

};

if (!isOpen) return null;

return (

<>

className="fixed bottom-0 left-0 right-0 bg-neutral-800 rounded-t-2xl z-50 max-h-[90vh]"

style={{

transform: translateY(${dragY}px),

transition: isDragging ? 'none' : 'transform 0.3s ease-out',

}}

>

className="flex justify-center py-3 cursor-grab touch-none"

onTouchStart={handleTouchStart}

onTouchMove={handleTouchMove}

onTouchEnd={handleTouchEnd}

>

{title && (

{title}

)}

{children}

);

}

// Pull to Refresh

function PullToRefresh({

onRefresh,

children

}: {

onRefresh: () => Promise;

children: ReactNode;

}) {

const [isPulling, setIsPulling] = useState(false);

const [isRefreshing, setIsRefreshing] = useState(false);

const [pullDistance, setPullDistance] = useState(0);

const startY = useRef(0);

const containerRef = useRef(null);

const THRESHOLD = 80;

const handleTouchStart = (e: React.TouchEvent) => {

if (containerRef.current?.scrollTop === 0) {

startY.current = e.touches[0].clientY;

setIsPulling(true);

}

};

const handleTouchMove = (e: React.TouchEvent) => {

if (!isPulling || isRefreshing) return;

const diff = e.touches[0].clientY - startY.current;

if (diff > 0) {

setPullDistance(Math.min(diff 0.5, THRESHOLD 1.5));

}

};

const handleTouchEnd = async () => {

if (!isPulling) return;

if (pullDistance >= THRESHOLD && !isRefreshing) {

setIsRefreshing(true);

setPullDistance(THRESHOLD);

try { await onRefresh(); }

finally { setIsRefreshing(false); }

}

setIsPulling(false);

setPullDistance(0);

};

return (

ref={containerRef}

className="h-full overflow-y-auto"

onTouchStart={handleTouchStart}

onTouchMove={handleTouchMove}

onTouchEnd={handleTouchEnd}

>

className="flex items-center justify-center overflow-hidden"

style={{ height: pullDistance }}

>

{isRefreshing ? (

) : (

rotate(${(pullDistance / THRESHOLD) * 180}deg) }}>↓

)}

{children}

);

}

// Swipeable Row

function SwipeableRow({

children,

onSwipeLeft,

onSwipeRight,

leftAction,

rightAction,

}: {

children: ReactNode;

onSwipeLeft?: () => void;

onSwipeRight?: () => void;

leftAction?: ReactNode;

rightAction?: ReactNode;

}) {

const [translateX, setTranslateX] = useState(0);

const startX = useRef(0);

const isDragging = useRef(false);

const THRESHOLD = 80;

const handleTouchStart = (e: React.TouchEvent) => {

startX.current = e.touches[0].clientX;

isDragging.current = true;

};

const handleTouchMove = (e: React.TouchEvent) => {

if (!isDragging.current) return;

const diff = e.touches[0].clientX - startX.current;

setTranslateX(Math.max(-100, Math.min(100, diff)));

};

const handleTouchEnd = () => {

isDragging.current = false;

if (translateX > THRESHOLD) onSwipeRight?.();

else if (translateX < -THRESHOLD) onSwipeLeft?.();

setTranslateX(0);

};

return (

{leftAction && (

{leftAction}

)}

{rightAction && (

{rightAction}

)}

className="relative bg-neutral-800"

style={{

transform: translateX(${translateX}px),

transition: isDragging.current ? 'none' : 'transform 0.2s ease-out',

}}

onTouchStart={handleTouchStart}

onTouchMove={handleTouchMove}

onTouchEnd={handleTouchEnd}

>

{children}

);

}

```

Usage Examples

```typescript

export default function DashboardPage() {

const [sheetOpen, setSheetOpen] = useState(false);

const handleRefresh = async () => {

await fetchData();

};

return (

{items.map((item) => (

key={item.id}

onSwipeLeft={() => deleteItem(item.id)}

rightAction={πŸ—‘οΈ}

>

setSheetOpen(true)}>

{item.title}

))}

setSheetOpen(false)} title="Details">

Sheet content here

{ href: '/dashboard', label: 'Home', icon: '🏠' },

{ href: '/settings', label: 'Settings', icon: 'βš™οΈ' },

]} />

);

}

```

Best Practices

  1. Minimum touch target: 44x44px (Apple) / 48x48dp (Google)
  2. Use safe-area-bottom for bottom navigation on notched devices
  3. Always provide visual feedback on touch (active states)
  4. Lock body scroll when sheets are open
  5. Use touch-none on drag handles to prevent scroll interference

Common Mistakes

  • Touch targets too small (causes mis-taps)
  • Not handling safe areas (content hidden behind notch)
  • Missing active states (no touch feedback)
  • Forgetting to unlock body scroll on unmount
  • Not debouncing pull-to-refresh

Related Patterns

  • design-tokens (consistent spacing/sizing)
  • pwa-setup (full mobile experience)

More from this repository10

🎯
feature-flags🎯Skill

Enables controlled feature rollouts, A/B testing, and selective feature access through configurable flags for gradual deployment and user targeting.

🎯
design-tokens🎯Skill

Generates a comprehensive, type-safe design token system with WCAG AA color compliance and multi-framework support for consistent visual design.

🎯
file-uploads🎯Skill

Securely validates, scans, and processes file uploads with multi-stage checks, malware detection, and race condition prevention.

🎯
ai-coaching🎯Skill

Guides users through articulating creative intent by extracting structured parameters and detecting conversation readiness.

🎯
environment-config🎯Skill

Validates and centralizes environment variables with type safety, fail-fast startup checks, and multi-environment support.

🎯
community-feed🎯Skill

Generates efficient social feed with cursor pagination, trending algorithms, and engagement tracking for infinite scroll experiences.

🎯
cloud-storage🎯Skill

Enables secure, multi-tenant cloud file storage with signed URLs, direct uploads, and visibility control for user-uploaded assets.

🎯
email-service🎯Skill

Simplifies email sending, templating, and tracking with robust SMTP integration and support for multiple email providers and transactional workflows.

🎯
error-sanitization🎯Skill

Sanitizes error messages by logging full details server-side while exposing only generic, safe messages to prevent sensitive information leakage.

🎯
batch-processing🎯Skill

Optimizes database operations by collecting and batching independent records, improving throughput by 30-40% with built-in fallback processing.