원클릭으로
design-to-flutter
Convert design specifications from Figma, Sketch, or Penpot to Flutter widgets with proper theming
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Convert design specifications from Figma, Sketch, or Penpot to Flutter widgets with proper theming
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Strategic search planning for agent-driven research. Generates structured search-term matrices with tiered fallback strategies, engine-specific operators, and grading criteria before executing any searches. Use this skill whenever research requires more than a single search query — comparing technologies, verifying claims across sources, surveying a landscape, investigating a multi-faceted question, or building evidence for a decision. Do NOT use for quick factual lookups, fetching a single known URL, or questions answerable from a single source. Covers tech, academic, regulatory, and general domains. Think of it as "research planning" — the matrix is the plan, execution comes after.
Create language conversion skills for translating code from language A to language B. Use when building 'convert-X-Y' skills, designing type mappings between languages, establishing idiom translation patterns, or defining conversion methodologies. Provides foundational patterns that specific conversion skills extend.
Guide for translating code between programming languages. Use when converting code from one language to another, planning language migrations, understanding conversion challenges, asking about type mappings, idiom translations, or referencing pattern mappings. Covers APTV workflow, type systems, error handling, concurrency, and language-specific gotchas.
Identify skill coverage gaps and improvement opportunities. Use when analyzing missing skills for a task, creating skill gap issues, evaluating skill effectiveness, or refining skill progressive disclosure.
Validate Claude Code skills against best practices. Use when checking skill quality, running validation, or creating improvement issues.
{{DESCRIPTION}}
| name | design-to-flutter |
| description | Convert design specifications from Figma, Sketch, or Penpot to Flutter widgets with proper theming |
| created | "2026-02-22T00:00:00.000Z" |
| updated | "2026-02-22T00:00:00.000Z" |
| tags | ["design","flutter","dart","widgets","material"] |
| requires | design-tokens-extraction |
Convert design component specifications to Flutter widgets.
This skill transforms design specifications into idiomatic Flutter widgets with proper theming, composition patterns, and Material/Cupertino compliance.
This skill covers:
This skill does NOT cover:
design-tokens-extraction skill)| 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 |
| 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 |
Read the design component and identify:
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,
),
),
],
],
),
),
);
}
}
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),
);
}
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;
}
}
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),
],
],
),
),
),
);
}
}
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!,
],
],
),
),
);
}
}
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);
});
});
}
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
design-tokens-extraction skill - Extract tokens firstdesign-token-json style - Token format referencecomponent-spec style - Documentation format