Skip to content
Back to skills

Design To Flutter

ASecurity

Convert design specifications from Figma, Sketch, or Penpot to Flutter widgets with proper theming

  • 8 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 2, 2026
documentationdocumentation

Security analysis

A100/100

Scanned June 2, 2026

npx -y skills add aRustyDev/agents --skill design-to-flutter --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Design To Flutter?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Design To Flutter
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arustydev-design-to-flutter/badge)](https://www.skillsdirectory.com/skills/arustydev-design-to-flutter)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: design-to-flutter
description: Convert design specifications from Figma, Sketch, or Penpot to Flutter widgets with proper theming
created: 2026-02-22
updated: 2026-02-22
tags: [design, flutter, dart, widgets, material]
requires: design-tokens-extraction
---

# Design to Flutter

Convert design component specifications to Flutter widgets.

## Overview

This skill transforms design specifications into idiomatic Flutter widgets with proper theming, composition patterns, and Material/Cupertino compliance.

**This skill covers:**

- Converting design frames to Flutter widget trees
- Mapping design properties to widget parameters
- Generating theme-aware widgets
- Creating widget tests

**This skill does NOT cover:**

- Design token extraction (see `design-tokens-extraction` skill)
- State management (Provider, Riverpod, Bloc)
- Navigation and routing

## Quick Reference

### Design to Flutter Mapping

| Design Element | Flutter Widget |
|----------------|----------------|
| Frame/Group | `Container`, `SizedBox` |
| Auto-layout horizontal | `Row` |
| Auto-layout vertical | `Column` |
| Overlapping layers | `Stack` |
| Text | `Text` |
| Rectangle | `Container` with `BoxDecoration` |
| Circle | `Container` with `BoxShape.circle` |
| Image | `Image.asset` / `Image.network` |
| Scroll | `SingleChildScrollView`, `ListView` |
| Grid | `GridView` |

### Layout Properties

| Design Property | Flutter Equivalent |
|-----------------|-------------------|
| Padding | `Padding` widget or `padding` parameter |
| Gap/Spacing | `SizedBox` or `MainAxisAlignment.spaceBetween` |
| Alignment | `MainAxisAlignment`, `CrossAxisAlignment` |
| Fill container | `Expanded`, `Flexible` |
| Fixed size | `SizedBox`, `Container` with constraints |

## Workflow: Convert Design Component

### Step 1: Analyze Design Structure

Read the design component and identify:

- Widget hierarchy and nesting
- Layout direction and spacing
- Theme token usage
- Interactive elements

### Step 2: Generate Widget Code

```dart
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';

class ComponentName extends StatelessWidget {
  final String title;
  final String? subtitle;
  final VoidCallback? onTap;

  const ComponentName({
    super.key,
    required this.title,
    this.subtitle,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: EdgeInsets.all(AppTheme.spacing.md),
        decoration: BoxDecoration(
          color: theme.colorScheme.surface,
          borderRadius: BorderRadius.circular(AppTheme.radius.md),
          boxShadow: [AppTheme.shadows.sm],
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: theme.textTheme.bodyLarge,
            ),
            if (subtitle != null) ...[
              SizedBox(height: AppTheme.spacing.xs),
              Text(
                subtitle!,
                style: theme.textTheme.bodySmall?.copyWith(
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}
```

### Step 3: Generate Theme Extensions

```dart
import 'package:flutter/material.dart';

class AppTheme {
  // Spacing scale
  static const spacing = _Spacing();

  // Border radius
  static const radius = _Radius();

  // Shadows
  static const shadows = _Shadows();

  // Light theme
  static ThemeData get light => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF2196F3),
      brightness: Brightness.light,
    ),
    textTheme: _textTheme,
  );

  // Dark theme
  static ThemeData get dark => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF2196F3),
      brightness: Brightness.dark,
    ),
    textTheme: _textTheme,
  );

  static const _textTheme = TextTheme(
    displayLarge: TextStyle(fontSize: 57, fontWeight: FontWeight.w400),
    displayMedium: TextStyle(fontSize: 45, fontWeight: FontWeight.w400),
    headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.w600),
    headlineMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.w600),
    bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.w400),
    bodyMedium: TextStyle(fontSize: 14, fontWeight: FontWeight.w400),
    labelLarge: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
  );
}

class _Spacing {
  const _Spacing();

  double get xs => 4;
  double get sm => 8;
  double get md => 16;
  double get lg => 24;
  double get xl => 32;
}

class _Radius {
  const _Radius();

  double get sm => 4;
  double get md => 8;
  double get lg => 16;
  double get full => 9999;
}

class _Shadows {
  const _Shadows();

  BoxShadow get sm => BoxShadow(
    color: Colors.black.withOpacity(0.05),
    blurRadius: 2,
    offset: const Offset(0, 1),
  );

  BoxShadow get md => BoxShadow(
    color: Colors.black.withOpacity(0.1),
    blurRadius: 6,
    offset: const Offset(0, 4),
  );

  BoxShadow get lg => BoxShadow(
    color: Colors.black.withOpacity(0.1),
    blurRadius: 15,
    offset: const Offset(0, 10),
  );
}
```

## Common Patterns

### Card Widget

```dart
class AppCard extends StatelessWidget {
  final Widget child;
  final EdgeInsetsGeometry? padding;
  final VoidCallback? onTap;

  const AppCard({
    super.key,
    required this.child,
    this.padding,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    Widget card = Container(
      padding: padding ?? EdgeInsets.all(AppTheme.spacing.md),
      decoration: BoxDecoration(
        color: theme.colorScheme.surface,
        borderRadius: BorderRadius.circular(AppTheme.radius.lg),
        boxShadow: [AppTheme.shadows.md],
      ),
      child: child,
    );

    if (onTap != null) {
      return InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(AppTheme.radius.lg),
        child: card,
      );
    }

    return card;
  }
}
```

### Button Widget

```dart
enum ButtonVariant { primary, secondary, ghost }
enum ButtonSize { small, medium, large }

class AppButton extends StatelessWidget {
  final String label;
  final VoidCallback? onPressed;
  final ButtonVariant variant;
  final ButtonSize size;
  final IconData? leftIcon;
  final IconData? rightIcon;
  final bool isLoading;

  const AppButton({
    super.key,
    required this.label,
    this.onPressed,
    this.variant = ButtonVariant.primary,
    this.size = ButtonSize.medium,
    this.leftIcon,
    this.rightIcon,
    this.isLoading = false,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    final padding = switch (size) {
      ButtonSize.small => EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      ButtonSize.medium => EdgeInsets.symmetric(horizontal: 16, vertical: 12),
      ButtonSize.large => EdgeInsets.symmetric(horizontal: 24, vertical: 16),
    };

    final textStyle = switch (size) {
      ButtonSize.small => theme.textTheme.labelSmall,
      ButtonSize.medium => theme.textTheme.labelLarge,
      ButtonSize.large => theme.textTheme.labelLarge?.copyWith(fontSize: 16),
    };

    final (backgroundColor, foregroundColor) = switch (variant) {
      ButtonVariant.primary => (
        theme.colorScheme.primary,
        theme.colorScheme.onPrimary,
      ),
      ButtonVariant.secondary => (
        theme.colorScheme.secondaryContainer,
        theme.colorScheme.onSecondaryContainer,
      ),
      ButtonVariant.ghost => (
        Colors.transparent,
        theme.colorScheme.primary,
      ),
    };

    return Material(
      color: backgroundColor,
      borderRadius: BorderRadius.circular(AppTheme.radius.md),
      child: InkWell(
        onTap: isLoading ? null : onPressed,
        borderRadius: BorderRadius.circular(AppTheme.radius.md),
        child: Padding(
          padding: padding,
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (isLoading)
                SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(
                    strokeWidth: 2,
                    color: foregroundColor,
                  ),
                )
              else if (leftIcon != null)
                Icon(leftIcon, size: 20, color: foregroundColor),
              if (leftIcon != null || isLoading) SizedBox(width: 8),
              Text(label, style: textStyle?.copyWith(color: foregroundColor)),
              if (rightIcon != null) ...[
                SizedBox(width: 8),
                Icon(rightIcon, size: 20, color: foregroundColor),
              ],
            ],
          ),
        ),
      ),
    );
  }
}
```

### List Item Widget

```dart
class ListItem extends StatelessWidget {
  final String title;
  final String? subtitle;
  final Widget? leading;
  final Widget? trailing;
  final VoidCallback? onTap;

  const ListItem({
    super.key,
    required this.title,
    this.subtitle,
    this.leading,
    this.trailing,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return InkWell(
      onTap: onTap,
      child: Padding(
        padding: EdgeInsets.symmetric(
          horizontal: AppTheme.spacing.md,
          vertical: AppTheme.spacing.sm,
        ),
        child: Row(
          children: [
            if (leading != null) ...[
              leading!,
              SizedBox(width: AppTheme.spacing.md),
            ],
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(title, style: theme.textTheme.bodyLarge),
                  if (subtitle != null)
                    Text(
                      subtitle!,
                      style: theme.textTheme.bodySmall?.copyWith(
                        color: theme.colorScheme.onSurfaceVariant,
                      ),
                    ),
                ],
              ),
            ),
            if (trailing != null) ...[
              SizedBox(width: AppTheme.spacing.md),
              trailing!,
            ],
          ],
        ),
      ),
    );
  }
}
```

## Widget Test Template

```dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/widgets/component_name.dart';

void main() {
  group('ComponentName', () {
    testWidgets('renders title', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: ComponentName(title: 'Test Title'),
          ),
        ),
      );

      expect(find.text('Test Title'), findsOneWidget);
    });

    testWidgets('renders subtitle when provided', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: ComponentName(
              title: 'Title',
              subtitle: 'Subtitle',
            ),
          ),
        ),
      );

      expect(find.text('Subtitle'), findsOneWidget);
    });

    testWidgets('calls onTap when tapped', (tester) async {
      var tapped = false;

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: ComponentName(
              title: 'Title',
              onTap: () => tapped = true,
            ),
          ),
        ),
      );

      await tester.tap(find.byType(ComponentName));
      expect(tapped, isTrue);
    });
  });
}
```

## File Structure

```text
lib/
├── theme/
│   ├── app_theme.dart
│   ├── colors.dart
│   └── typography.dart
├── widgets/
│   ├── app_button.dart
│   ├── app_card.dart
│   ├── list_item.dart
│   └── widgets.dart  // barrel export
└── main.dart
```

## See Also

- `design-tokens-extraction` skill - Extract tokens first
- `design-token-json` style - Token format reference
- `component-spec` style - Documentation format

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.