karanb05/mobile-flutter-design-skill

mobile-flutter-design

Apply Apple Human Interface Guidelines to Flutter apps on iOS.

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/karanb05/mobile-flutter-design-skill --skill mobile-flutter-design

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

Flutter iOS Design

Adapts Apple's Human Interface Guidelines (HIG) to Flutter. The HIG principles are framework-agnostic; this skill re-expresses them in idiomatic Flutter so a Flutter app can feel native on iOS without leaving Dart. Converted from the SwiftUI-based mobile-ios-design skill.

When to Use This Skill

  • Designing/refining Flutter screens that must feel native on iOS
  • Choosing Cupertino vs. Material widgets for an iOS-leaning app
  • Getting spacing, typography, and hierarchy to HIG standards in Flutter
  • Implementing iOS-style navigation (stacks, tabs, bottom sheets) in Flutter
  • Supporting Dynamic Type (text scaling), Dark Mode, and accessibility
  • Making glanceable, uncluttered dashboards and lists

Cupertino vs. Material — the first decision

Flutter ships two design languages. Pick deliberately:

  • Material (`MaterialApp`, `Scaffold`, `NavigationBar`) — the pragmatic

default for cross-platform apps. Looks clean on iOS; not pixel-native but users accept it. If the app already commits to Material, stay in Material and apply HIG principles (spacing, hierarchy, restraint) rather than swapping widgets. Don't mix half-and-half — inconsistency reads as broken.

  • Cupertino (`CupertinoApp`, `CupertinoPageScaffold`, `CupertinoNavigationBar`)

— pixel-native iOS look (system fonts, iOS transitions, sliding back gesture). Use when "feels exactly like an Apple app" is a hard requirement, or for a specific surface (a settings screen, an action sheet).

  • Hybrid — Material shell + Cupertino for specific interactions

(showCupertinoModalPopup for action sheets, CupertinoContextMenu). Fine when intentional and consistent.

This project (FieldFlowSales mobile) is a Material app (NavigationBar, showModalBottomSheet, custom cards). Apply HIG at the principle level here; reach for Cupertino widgets only for genuinely iOS-idiomatic moments.

Core Concepts

1. HIG Principles (framework-agnostic)

  • Clarity — legible text, precise icons, subtle adornment. Prefer content

over chrome. In Flutter: lean on Theme.of(context).textTheme, keep borders and shadows light, avoid decorative gradients competing with content.

  • Deference — the UI helps users understand content without competing with

it. Translate to restraint: one accent color per surface, generous whitespace, no card-in-card-in-card nesting.

  • Depth — layers and motion convey hierarchy. In Flutter: elevation/shadow

for cards, showModalBottomSheet sliding up for transient tasks, hero/route transitions for drill-down.

2. Layout — SwiftUI → Flutter

SwiftUIFlutter
VStack(alignment:spacing:)Column(crossAxisAlignment:, children:[…, SizedBox(height:)…])
HStackRow
ZStackStack
Spacer()Spacer() or Expanded
.padding()Padding(padding: EdgeInsets…) / Container(padding:)
LazyVStack / ListListView / ListView.builder
LazyVGrid(adaptive)GridView.builder(gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 200))
LazyVGrid(fixed 3)GridView.count(crossAxisCount: 3)
dart
// VStack(alignment: .leading, spacing: 12) equivalent
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Title', style: Theme.of(context).textTheme.titleMedium),
    const SizedBox(height: 12),
    Text('Subtitle',
        style: Theme.of(context).textTheme.bodyMedium
            ?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant)),
  ],
)

// HStack with Spacer + trailing accent
Row(
  children: [
    Icon(Icons.star, color: Colors.amber),
    const SizedBox(width: 8),
    const Text('Featured'),
    const Spacer(),
    Text('View All', style: TextStyle(color: Theme.of(context).colorScheme.primary)),
  ],
)

Spacing scale: HIG leans on multiples of 4/8. Use a consistent scale (4, 8, 12, 16, 24) rather than arbitrary values. Standard screen edge padding is 16.

3. Navigation

SwiftUIFlutter
NavigationStack + NavigationLinkNavigator + MaterialPageRoute (or go_router)
.navigationTitleAppBar(title:)
TabViewScaffold(bottomNavigationBar: NavigationBar) + IndexedStack
.sheetshowModalBottomSheet
.confirmationDialogshowModalBottomSheet of actions, or showCupertinoModalPopup
.fullScreenCoverNavigator.push with fullscreenDialog: true

Full patterns and code in references/flutter-navigation.md. Key HIG rule: push for drill-down (a back stack), sheet for a transient/self-contained task, tabs for peer sections. Keep tab pages alive with IndexedStack so switching doesn't rebuild/refetch (this app learned that the hard way — it fixed a tab flicker).

4. System Integration

  • Icons: SF Symbols → Flutter Icons (Material) or CupertinoIcons. For an

iOS feel use CupertinoIcons; for a Material app, Icons is consistent. Pick one set per app.

  • Dynamic Type: SwiftUI scales automatically; Flutter needs care. Never

hardcode font sizes for body copy where you can use textTheme. Respect the user's setting via MediaQuery.textScalerOf(context) — don't clamp it to 1.0 (that breaks accessibility). Test at 200% text size; ensure rows grow, not clip.

  • Haptics: HapticFeedback.selectionClick() / .lightImpact() on

meaningful actions (toggle, pull-to-refresh complete) — iOS users expect it.

5. Visual Design

  • Semantic colors (dark-mode safe): SwiftUI's .primary/.secondary →

Flutter's ColorScheme. Use Theme.of(context).colorScheme.onSurface, onSurfaceVariant, surface, primary instead of literal Colors.black/ Colors.grey.shadeXXX. Literal colors are the #1 cause of broken dark mode. If the app is light-only today, still prefer colorScheme so dark mode is a theme change, not a rewrite.

  • Materials/blur: SwiftUI .ultraThinMaterial → BackdropFilter with

ImageFilter.blur behind a translucent color (use sparingly; it's costly).

  • Elevation: card shadow via `BoxShadow(color: Colors.black.withValues(alpha:

0.06), blurRadius: 8, offset: Offset(0, 4)) or Material(elevation:)`. HIG shadows are subtle — low opacity, soft blur. Avoid heavy drop shadows.

  • Corner radius: 12 for cards, 20+ for pills/sheets, matching iOS softness.

Quick Start Component (SwiftUI FeatureCard → Flutter)

dart
class FeatureCard extends StatelessWidget {
  final String title;
  final String description;
  final IconData icon;
  final VoidCallback? onTap;

  const FeatureCard({
    super.key,
    required this.title,
    required this.description,
    required this.icon,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    return InkWell(
      onTap: onTap,
      borderRadius: BorderRadius.circular(12),
      child: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: scheme.surface,
          borderRadius: BorderRadius.circular(12),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withValues(alpha: 0.05),
              blurRadius: 4,
              offset: const Offset(0, 2),
            ),
          ],
        ),
        child: Row(
          children: [
            Container(
              width: 44,
              height: 44,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                color: scheme.primary.withValues(alpha: 0.1),
                shape: BoxShape.circle,
              ),
              child: Icon(icon, color: scheme.primary),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(title, style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 4),
                  Text(
                    description,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: Theme.of(context).textTheme.bodyMedium
                        ?.copyWith(color: scheme.onSurfaceVariant),
                  ),
                ],
              ),
            ),
            Icon(Icons.chevron_right, color: scheme.outline),
          ],
        ),
      ),
    );
  }
}

Best Practices (Flutter-adapted)

  1. Prefer `ColorScheme`/`textTheme` over literals — automatic dark mode and

consistent typography. Reserve literal colors for brand accents only.

  1. One icon set per app (Icons or CupertinoIcons) — don't mix.
  2. Respect text scaling — use textTheme, don't hardcode body sizes, don't

clamp textScaler. Let rows grow.

  1. Add semantics — wrap icon-only buttons with Semantics(label:) or use

IconButton(tooltip:); label meaningful images. Test with TalkBack/VoiceOver.

  1. Respect safe areas — wrap edge content in SafeArea; don't hardcode

status-bar/home-indicator padding.

  1. Keep tab pages alive — IndexedStack (or AutomaticKeepAliveClientMixin)

so switching tabs doesn't refetch and flicker.

  1. Lazy lists — ListView.builder/GridView.builder for long content, never

a Column of hundreds of children.

  1. Glanceable first — put what a user needs at a glance at the top; collapse

detail behind taps (e.g. a summary row that expands). Cards should be slim.

  1. Test on device — haptics, scroll physics, and true colors don't show in a

simulator screenshot.

Common Issues (Flutter)

  • Broken dark mode — literal Colors.black/Colors.grey.shadeXXX. Use

colorScheme.

  • Clipped text at large font sizes — fixed heights on rows containing text;

let them size to content, use Flexible/Expanded.

  • Jank on long lists — non-lazy Column/ListView(children:[…]); switch to

.builder.

  • Overflowing dialogs/sheets — unbounded content in a Dialog; give the body

overflow-y via a scroll view and cap tall inner lists (this app hit exactly this in a modal — DialogContent was display:grid, so a flex child didn't scroll; the fix was overflow on the content itself + a height-capped list).

  • Tab flicker / lost scroll — recreating tab pages on switch; use

IndexedStack.

  • Nested-scroll gesture conflict — a scrollable inside a scrollable fighting

for the drag. Avoid unless the inner region genuinely needs its own scroll; prefer letting the page scroll.

References

  • references/flutter-navigation.md — stacks, tabs, bottom sheets, dialogs,

deep links, transitions (SwiftUI navigation → Flutter).

  • references/flutter-components.md — lists, forms/inputs, buttons/menus,

sheets, loading/skeletons, async images, animations, gestures (SwiftUI components → Flutter).

관련 작업