karanb05/mobile-flutter-design-skill

mobile-flutter-design

Apply Apple Human Interface Guidelines to Flutter apps on iOS.

Ver código fuente

Cómo usar

  1. Copia el comando y revisa el paquete antes de instalarlo en tu entorno de agente.
  2. Consulta las herramientas, credenciales y entornos necesarios en el documento original.
  3. Describe las entradas y el resultado deseado; contrasta la salida con las fuentes.
npx skills add https://github.com/karanb05/mobile-flutter-design-skill --skill mobile-flutter-design

Antes de empezar

  • La instalación no incluye suscripción al modelo, cuota API ni permisos sobre tus archivos.
  • Las estrellas e instalaciones reflejan actividad, no una prueba independiente de calidad o seguridad.
Documento original del Skill

Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.

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

Relacionados por tarea