
How to Replicate Figma Designs in Flutter — A Guide to Pixel-Perfect UI Replication
How to Replicate Figma Designs in Flutter — A Guide to Pixel-Perfect UI Replication 관련
Successfully translating a Figma design into a Flutter application requires more than just placing elements on the screen. The objective is to achieve pixel-perfect fidelity, meaning that the Flutter app must precisely mirror the designer's original vision. This involves paying close attention to every detail, from shadows and curve radii to line heights and spacing. A small discrepancy in any of these areas can alter the intended look and feel of the user interface.
This comprehensive guide provides actionable strategies and practical methods for developers. It covers the specific steps and considerations needed to bridge the gap between design files and functional code. By following the practices outlined here, you will be able to transform static Figma artboards into high-quality, fully functional Flutter UIs that exactly match the design specifications.
- Use the Figma "Inspect" Panel: Your Blueprint for Precision
- Implement a Consistent Spacing & Sizing System: The End of Magic Numbers
- Replicate Typography with Absolute Fidelity: Beyond Basic Fonts
- Deconstruct Layouts with Flutter's Primitives: Column, Row, Stack, and Spacer
- Master BoxDecoration: The Aesthetic Workhorse
- Utilize ClipRRect and Overflow Management: Handling the Intangibles
- Leverage FittedBox and AspectRatio: Maintaining Proportions
- Replicate Opacity and Blending Modes: The Subtle Layers
- Implement Vectors and Icons with Scalability: Sharpness at Any Scale
- Master Componentization and Reusability: Building Scalable UIs
- Scrutinize Interactive States: Buttons, Inputs, and Others
- Cross-Reference and Iterate Continuously: The Verification Loop
- Understand Design System Thinking: Beyond Individual Components
- Embrace Constraints and Responsiveness: Adapting to All Screens
- Handling Assets Efficiently: Images and SVGs
- Accessibility Considerations: Designing for Everyone
- Performance Optimization during Replication
- Version Control and Collaboration: Working with Teams
- Handling Edge Cases and Data Dynamics
- Implement Vectors and Icons with Scalability: Achieving Visual Consistency Across All Resolutions
- Self-Correction and Learning from Mistakes
- Project Overview
Prerequisites
To effectively follow and implement the strategies outlined in the comprehensive guide for pixel-perfect Figma to Flutter replication, you should ideally possess the following prerequisites:
- Dart Programming Language:
- Core Concepts: Solid understanding of Dart's syntax, data types, variables, control flow (
if/else
, loops), functions, classes, objects, and asynchronous programming (Futures,async
/await
). - Null Safety: Familiarity with Dart's null safety features.
- Core Concepts: Solid understanding of Dart's syntax, data types, variables, control flow (
- Flutter SDK & Development Environment:
- Installation & Setup: Flutter SDK correctly installed and configured on your machine.
- IDE Proficiency: Familiarity with a Flutter-compatible IDE like VS Code or Android Studio, including running/debugging apps, using hot reload/restart.
- Basic Project Structure: Understanding of a typical Flutter project's directory structure (
lib
,assets
,pubspec.yaml
, etc.).
- Fundamental Flutter Concepts:
- Widget Tree: A clear understanding of how Flutter's widget tree works, including parent-child relationships and widget composition.
- StatelessWidget & StatefulWidget: Ability to differentiate and appropriately use
StatelessWidget
for static UI andStatefulWidget
for dynamic/interactive UI. - Build Context: Understanding of
BuildContext
and its role in the widget tree.
- Figma Interface Navigation:
- Ability to open and navigate Figma files and artboards.
- Understanding of layers, groups, and frames.
- Figma "Inspect" Panel Mastery:
- Crucial: Proficient use of the "Inspect" panel to extract precise values for:
- Dimensions (width, height)
- Spacing (padding, margin, gap between elements)
- Colors (hex codes, RGB, opacity)
- Typography (font family, weight, size, line height, letter spacing)
- Borders (radius, width, color)
- Shadows (offset, blur, spread, color, opacity)
- Gradients (colors, stops, angle)
- Understanding Auto Layout: Basic comprehension of how Auto Layout works in Figma, as it often dictates Flutter's
Column
/Row
andSpacer
/Expanded
usage.
- Crucial: Proficient use of the "Inspect" panel to extract precise values for:
- Figma Asset Export:
- Knowledge of how to select and export various assets from Figma (images, SVGs) in the correct formats and resolutions.
- Version Control (Git):
- Basic understanding of Git commands (clone, add, commit, push, pull, branch). This is essential for collaborative development and managing code changes.
- Debugging Skills:
- Ability to use your IDE's debugger to inspect widget trees, variable values, and diagnose issues.
- Familiarity with Flutter DevTools for UI inspection and performance profiling.
- Problem-Solving:
- A logical approach to breaking down complex problems into smaller, manageable parts.
- Patience and persistence in troubleshooting visual discrepancies.
Use the Figma "Inspect" Panel: Your Blueprint for Precision
The "Inspect" panel in Figma is your single most valuable resource. Before you write a single line of code, spend significant time dissecting every element here. Think of it as your precise blueprint.
- Exact Dimensions: Don't approximate. Note down exact
width
andheight
values, even if they have decimals (for example,123.45px
). Flutter'sdouble
values perfectly accommodate this precision. - Granular Spacing: Examine
margin
andpadding
from all four sides. Are they uniform, or is there asymmetry? This dictates whether you useEdgeInsets.all()
,EdgeInsets.symmetric()
, or the more specificEdgeInsets.fromLTRB()
. - Positioning Logic: Understand if an element is absolutely positioned or part of an Auto Layout frame. This crucial distinction determines whether you'll employ
Positioned
widgets within aStack
or rely onColumn
/Row
withmainAxisAlignment
andcrossAxisAlignment
. - Typographical Deep Dive: Extract the exact font family, weight (for example, "Inter Medium," "Inter Bold"), size, line height, letter spacing, and color. Every one of these properties has a direct counterpart in Flutter's
TextStyle
. - Color Codes: Copy the hex codes exactly. Always use
Color(0xFFRRGGBB)
in Flutter to ensure exact color matching, including the alpha channel if specified. - Borders & Shadows: Extract border radius, color, width, and for shadows, the x/y offset, blur, spread, and color. These translate directly to
BoxDecoration
andBoxShadow
properties. - Gradients: If a gradient is present, meticulously note its angle, the precise colors involved, and their respective stops. Flutter's
LinearGradient
orRadialGradient
will be your tools here.
Implement a Consistent Spacing & Sizing System: The End of Magic Numbers
Randomly hardcoding 16.0
, 8.0
, or 24.0
throughout your codebase is a recipe for inconsistency and maintenance headaches. Establish a design system for spacing and sizing.
- Identify the Base Unit: Figma designs often implicitly use a base spacing unit (for example, all paddings are multiples of 4 or 8 pixels). Identify this consistent increment.
- Centralized Constants: Create a dedicated file, perhaps
lib/utils/
app_dimensions.dart
, to store your spacing and sizing variables.
class AppDimensions {
static const double spacingSmall = 8.0;
static const double spacingMedium = 16.0;
static const double spacingLarge = 24.0;
static const double iconSizeMedium = 24.0;
// ... and so on for all consistent measurements
}
- Consistent Usage: Always refer to these constants in your widgets:
Padding(
padding: const EdgeInsets.all(AppDimensions.spacingMedium),
child: // ...
),
SizedBox(width: AppDimensions.spacingSmall),
- Benefit: This approach not only ensures consistent visual rhythm but also simplifies global design adjustments.
Replicate Typography with Absolute Fidelity: Beyond Basic Fonts
Text is a cornerstone of UI design. Achieving pixel-perfect typography means going beyond just selecting the right font family.
- Custom Font Integration: If your Figma design uses custom fonts, you must correctly add them to your
pubspec.yaml
and ensure they load properly in your Flutter app. - Precise Font Weights: Distinguish meticulously between
FontWeight.w400
(Regular),w500
(Medium),w600
(SemiBold),w700
(Bold), and so on. The Figma inspect panel will provide the exact weight. fontSize
Accuracy: Use the exact pixel size from Figma for yourfontSize
property inTextStyle
.- Line Height (
height
): This is paramount for vertical text spacing. Figma's "Line Height" property, often expressed as a percentage or pixel value, needs conversion. If Figma states24px
line height for a16px
font size, yourTextStyle
height
property should be24 / 16 = 1.5
. letterSpacing
: Directly apply the letter spacing value from Figma (which is often in pixels and translates directly to Flutter'sletterSpacing
property).textBaseline
(Advanced): For very specific multi-font or icon-with-text alignments, you might occasionally need to fine-tunetextBaseline
to match Figma's visual precision.
Deconstruct Layouts with Flutter's Primitives: Column
, Row
, Stack
, and Spacer
Flutter's declarative layout system offers powerful primitives. Learning to map Figma's visual arrangements to these widgets is key.
- Primary Flow: Determine if the main flow of elements is horizontal (
Row
) or vertical (Column
). - Overlapping Elements (
Stack
): If elements are layered or positioned on top of each other in Figma, aStack
widget combined withPositioned
children is the correct approach. Do not force overlapping elements intoColumn
/Row
with complex negative margins or paddings. - Content Distribution:
mainAxisAlignment
: Use this to distribute children along the main axis of aRow
orColumn
(for example,start
,center
,end
,spaceBetween
,spaceAround
,spaceEvenly
).crossAxisAlignment
: Use this to align children along the cross axis (for example,start
,center
,end
,stretch
,baseline
).
- Flexible Spacing (
Spacer
): Replicate Figma's "stretch" or "fill available space" auto-layout behaviors usingSpacer()
widgets withinRow
orColumn
. - Adaptive Sizing (
Expanded
,Flexible
): When elements need to take up remaining space or be constrained within a certain proportion,Expanded
andFlexible
are essential. Mimic Figma's "Fill Container" or "Fixed Width" behaviors precisely.
Master BoxDecoration
: The Aesthetic Workhorse
BoxDecoration
is your primary tool for replicating the visual aesthetics of containers in Figma, including backgrounds, borders, shadows, and gradients.
color
: The background color of the container, directly from Figma's hex code.borderRadius
: Match Figma's exact corner radii. UseBorderRadius.circular()
for uniform corners,BorderRadius.only()
for specific corners, orBorderRadius.all(Radius.elliptical(x, y))
for more complex shapes.border
: Replicate border styles usingBorder.all(color: ..., width: ...)
or more specific options likeBorder.symmetric()
orBorderDirectional()
if individual sides have different styles.boxShadow
: This is where minute details truly matter. Extract every value:
boxShadow: [
BoxShadow(
color: Color(0x33000000), // Exact color, including opacity (alpha channel)
offset: Offset(0, 4), // Exact X and Y offset
blurRadius: 8, // Exact blur radius
spreadRadius: 0, // Exact spread radius (often 0, but always verify)
),
],
gradient
: Precisely translate linear or radial gradients:
gradient: LinearGradient(
begin: Alignment.topLeft, // Or specific angles derived from Figma
end: Alignment.bottomRight,
colors: [Color(0xFF00FF00), Color(0xFF0000FF)], // Exact colors
stops: [0.0, 1.0], // Exact color stop positions
),
Utilize ClipRRect
and Overflow Management: Handling the Intangibles
Sometimes, elements in Figma might appear to "spill out" or be precisely cropped. Understanding Flutter's clipping and overflow behavior is critical.
ClipRRect
for Rounded Corners: If a child widget's content needs to be clipped to a parent's rounded corners (for example, an image within a card), wrap the child inClipRRect
. Don't solely rely on the parent'sBoxDecoration
, especially in complex hierarchies.- Overflow Behavior (
OverflowBox
): Figma designs might show elements extending beyond a frame's boundaries. By default,Column
/Row
clip content (overflow: Clip.hardEdge
). If you need content to be visible outside its immediate parent, you might need aStack
or explicitly manage overflow, potentially using anOverflowBox
for specific scenarios. - Extended Shadows: If a shadow in Figma extends significantly beyond its element, ensure your
BoxShadow
spreadRadius
andoffset
values are accurate. Also, confirm that the parent container allows for this visual extension (for example, it doesn't haveclipBehavior: Clip.hardEdge
if clipping is not desired).
Leverage FittedBox
and AspectRatio
: Maintaining Proportions
Images and content blocks often need to scale proportionally or fit within specific areas. These widgets are indispensable for responsive design.
FittedBox
: Excellent for ensuring a child widget (like an icon or text block) scales to fit its parent, while maintaining its original aspect ratio. Carefully consider thefit
properties likecontain
,cover
,fill
, andscaleDown
to match Figma's behavior.AspectRatio
: Crucial for images, videos, or any container where the ratio of width to height must be maintained regardless of the available screen space.
AspectRatio(
aspectRatio: 16 / 9, // Derived directly from Figma's image dimensions
child: Image.network('...'),
),
- Smart Image Sizing: Avoid setting fixed
width
andheight
onImage.network
orImage.asset
unless the design explicitly dictates a static size. Instead, think about how the image scales and fills its container in Figma.
Replicate Opacity and Blending Modes: The Subtle Layers
Subtle effects often define the "feel" of a design. Don't overlook transparency and blending.
Opacity
Widget: Use this widget for general element transparency.- Alpha Channel in Colors: For background colors, borders, or text colors with transparency, always include the alpha channel in your hex code (for example,
0x80RRGGBB
for 50% opacity). ColorFiltered
orShaderMask
(Advanced Blending): While less common for everyday designs, if Figma utilizes complex blending modes (for example, "Multiply," "Screen," "Overlay"), you'll need to exploreColorFiltered
with itsblendMode
property or, for more advanced custom effects,ShaderMask
. Look for subtle color interactions where one visual layer directly affects another.
Implement Vectors and Icons with Scalability: Sharpness at Any Scale
Rasterizing icons or complex vector shapes is a common mistake that leads to blurriness on different screen densities. Embrace vector graphics.
- SVG Icons: Always export icons from Figma as SVGs. Leverage a library like
flutter_svg
to render them in your Flutter app, ensuring crispness and scalability across all device resolutions. CustomPaint
for Unique Shapes: For highly unique, non-standard shapes, illustrations, or complex dividers,CustomPainter
is your ultimate tool. This requires translating Figma's vector paths (Bezier curves, lines) into Flutter'sPath
object. This is the epitome of "deep attention to detail" for custom graphics.- Icon Fonts: For standard icon sets (for example, Material Icons, Font Awesome), use Flutter's built-in
Icon
widget or import the specific icon font family into yourpubspec.yaml
and reference it in yourIcon
widget.
Master Componentization and Reusability: Building Scalable UIs
Figma's components are not just for design, they're a direct hint at how to structure your Flutter code.
- Identify Figma Components: Every button, card, input field, or navigation bar that's a component in Figma should ideally be a reusable
StatelessWidget
orStatefulWidget
in Flutter. - Prop-Based Customization: Design your Flutter components to accept parameters (props) for text, colors, icons, and interactive behaviors, just like Figma components have variants or properties.
- Theme Integration: Leverage Flutter's
ThemeData
to define global styles for colors, typography, and widget behaviors. This mirrors Figma's design tokens and ensures consistency across your app. - Shared Styles: Create classes or constants for frequently used
TextStyle
orBoxDecoration
to centralize your design language.
Scrutinize Interactive States: Buttons, Inputs, and Others
Design isn't static. Replicating interactive states is a critical, often overlooked, detail.
- Hover, Press, Focus: Figma designs often include states for buttons (hover, pressed), input fields (focused, error), and other interactive elements. You must implement these in Flutter using
GestureDetector
,InkWell
,MaterialButton
,TextFormField
, and so on, and manage their state visually. - Animations: If Figma showcases micro-interactions or transitions, plan how to replicate them using
AnimatedContainer
,Hero
animations,PageTransitionsBuilder
, or customAnimationController
. - Disabled States: Ensure that disabled buttons or input fields are visually distinct and match their Figma counterparts in color, opacity, and cursor changes.
Cross-Reference and Iterate Continuously: The Verification Loop
Replication is not a one-time task, it's an iterative process of comparison and refinement.
- Side-by-Side Comparison: Always have your Flutter app running on a device or emulator right next to your Figma design. Ideally, take screenshots of your app and overlay them on the Figma design to spot discrepancies.
- Pixel-by-Pixel Scan: Literally zoom into both the Figma design and your running Flutter app. Look for:
- Off-by-One Errors: A single pixel difference in padding, border, or spacing.
- Subtle Color Shifts: Are the colors exactly the same? Account for monitor calibration, but strive for hex code matching.
- Font Rendering Nuances: Sometimes font rendering can subtly vary across platforms or Flutter's text engine. Adjust
letterSpacing
orheight
slightly if needed to achieve visual parity. - Shadow Fidelity: Are the shadows exactly as soft/hard, diffuse, and offset as in Figma?
- Alignment Precision: Even a tiny misalignment of text baselines or icon centers must be corrected.
- Automated Tools (If Applicable): While manual inspection is paramount, some plugins or third-party tools can assist in comparing Flutter UI with Figma, offering a quick initial check.
- Peer Review: A fresh pair of eyes from another developer can often spot details you've become blind to.
Understand Design System Thinking: Beyond Individual Components
Why it Matters: Figma files often represent a living design system. Understanding this philosophy helps you build a more robust and maintainable Flutter app.
Actionable Advice
- Design Tokens: Recognize how Figma uses "design tokens" (variables for colors, typography, spacing, shadows). Translate these directly into Flutter's
ThemeData
, customColor
andTextStyle
constants, and yourAppDimensions
class. - Component Libraries: Think of your Flutter widgets as a direct extension of the Figma component library. Each component in Figma should ideally correspond to a well-defined, reusable Flutter widget.
- Naming Conventions: Adopt consistent naming conventions in your code that mirror Figma's (for example,
primaryButton
,headline1TextStyle
). This creates a shared language between designers and developers.
Embrace Constraints and Responsiveness: Adapting to All Screens
Why it Matters: Figma designs are often fixed at a certain width (for example, 375px for mobile). Your Flutter app must be responsive and adapt gracefully to various screen sizes, orientations, and device types.
Actionable Advice
- Figma Constraints: Pay close attention to how elements are constrained in Figma (left/right, top/bottom, center, scale). These directly inform your use of
Flexible
,Expanded
,Align
,Positioned
, andFractionallySizedBox
in Flutter. MediaQuery
: UseMediaQuery.of(context).size
to get the current screen dimensions and adapt layouts accordingly. Avoid fixed pixel widths/heights for entire screens.- Layout Builders (
LayoutBuilder
,OrientationBuilder
): For more complex responsive layouts, useLayoutBuilder
to get the available constraints of a parent widget and adjust children based on that.OrientationBuilder
helps adapt to portrait vs. landscape modes. - Relative Units: Where possible, think in terms of percentages or fractions (
FractionallySizedBox
) rather than absolute pixel values for spacing and sizing that needs to scale.
Handling Assets Efficiently: Images and SVGs
Why it Matters: Proper asset management is crucial for performance and scalability.
Actionable Advice
- Export Formats: Discuss with designers the best export formats. For icons and simple illustrations, SVGs are king (
flutter_svg
library). For complex photos, PNG or WebP (with proper compression) are often preferred. - Resolution: For raster images (PNG, JPG), ensure designers export assets at 2x and 3x resolutions, and place them in
assets/images/2.0x/
andassets/images/3.0x/
directories respectively, so Flutter automatically picks the correct one for the device's pixel density. - Asset Bundling: Declare all your assets in
pubspec.yaml
under theassets:
section. - Image Caching: For network images, consider using
cached_network_image
to improve performance and user experience.
Accessibility Considerations: Designing for Everyone
Why it Matters: A pixel-perfect replica isn't truly complete if it's not accessible. Figma designs should ideally include accessibility annotations, but as a developer, you're the last line of defense.
Actionable Advice
- Semantic Widgets: Use Flutter's semantic widgets whenever possible (for example,
ElevatedButton
instead of a customContainer
with aGestureDetector
). These widgets often have built-in accessibility features. - Meaningful Labels: Provide
semanticsLabel
for icons and images that convey information to screen readers. - Color Contrast: While primarily a design responsibility, double-check color contrast ratios, especially for text, against WCAG guidelines. If the design is failing, flag it.
- Tap Targets: Ensure interactive elements have sufficiently large tap targets (minimum 48x48 logical pixels) even if the visual element is smaller, using
minWidth
on buttons orPadding
aroundIcons
.
Performance Optimization during Replication
Why it Matters: A beautiful UI is useless if it's janky. Code-level decisions impact performance.
Actionable Advice
const
Widgets: Useconst
constructor for widgets whenever possible. This tells Flutter that the widget can be reused without rebuilding, significantly improving performance. This is a common missed opportunity.RepaintBoundary
: For complex, static parts of your UI that don't change often but have many children or custom painting, consider wrapping them in aRepaintBoundary
to prevent unnecessary repaints of their children.- Avoid Deep Nesting: While Flutter's widget tree is deep, excessively deep nesting can sometimes lead to performance issues. Try to flatten your widget tree where logical (for example, using
Wrap
instead of many nestedRows
for flow layouts). - Profile Your App: Use Flutter DevTools's Performance tab to identify UI flaws and memory leaks early in the development process.
Version Control and Collaboration: Working with Teams
Why it Matters: UI development is rarely a solo endeavor. Effective team collaboration is essential.
Actionable Advice
- Git Best Practices: Use Git for version control. Create feature branches for specific UI screens or components. Commit frequently with descriptive messages.
- Code Reviews: Get your UI code reviewed by peers. They can spot missed details, potential performance issues, or suggest better widget usage.
- Communication with Designers: Maintain an open channel of communication. If a Figma detail is unclear, ask. If a replication is proving difficult, discuss alternatives. Use tools like Slack, Discord, or project management platforms to share progress and clarify requirements.
- Figma Plugins/Integrations: Explore Figma plugins that might aid developers (for example, extracting CSS/Flutter code snippets, though often these are just starting points).
Handling Edge Cases and Data Dynamics
Why it Matters: Designs often show ideal states. Real-world apps have varying data, empty states, and loading states.
Actionable Advice
- Empty States: Replicate any "empty state" designs from Figma (for example, empty shopping cart, no search results).
- Loading States: How does the UI look when data is being fetched (for example, skeletons, loading spinners)?
- Error States: What happens if an API call fails or input validation errors occur? Ensure these are designed and replicated.
- Dynamic Content: Consider how varying text lengths (short vs. long names), image aspect ratios from real data, or lists with many/few items will affect your layout. Test with diverse data.
Implement Vectors and Icons with Scalability: Achieving Visual Consistency Across All Resolutions
Rasterizing icons or complex vector shapes is a common mistake that leads to blurriness on different screen densities. Embrace vector graphics and ensure you're using the exact icons specified in the design.
Exact Icon Source Verification
- Figma Inspect Panel: For each icon, meticulously check the "Inspect" panel in Figma. Does it indicate a specific icon font (for example, Material Icons, Font Awesome), a custom SVG, or a raster image?
- Design System Documentation: Consult the design system documentation (if available) for the prescribed icon set and how they should be implemented.
SVG Icons (Preferred for Custom Icons)
- Export from Figma: Always export custom or unique icons from Figma as SVGs. This format ensures they scale without pixelation.
- Flutter Integration: Leverage the
flutter_svg
package (addflutter_svg: ^x.x.x
to yourpubspec.yaml
).
import 'package:flutter_svg/flutter_svg.dart';
// ...
SvgPicture.asset(
'assets/icons/my_custom_icon.svg',
width: 24.0, // Match Figma's exact size for initial layout
height: 24.0,
colorFilter: ColorFilter.mode(Colors.black, BlendMode.srcIn), // Match icon color if needed
);
Icon Fonts (Preferred for Standard Sets)
- Identify the Font: If Figma uses a standard icon font like Material Icons, Cupertino Icons, or Font Awesome, ensure you're using the correct one.
- Material/Cupertino Icons: For built-in Flutter icons, simply use the
Icon
widget with the appropriate constant:
Icon(
Icons.arrow_back,
size: 24.0, // Match Figma's exact size
color: Colors.blue, // Match Figma's exact color
);
Custom Icon Fonts (for example, Font Awesome, custom brand icons)
- Obtain Font Files: Get the
.ttf
font file(s) from your designer or the icon font provider. - Add to
pubspec.yaml
:
flutter:
fonts:
- family: MyCustomIcons
fonts:
- asset: assets/fonts/MyCustomIcons.ttf
- Reference in Code: Create a
static const IconData
class or use the directIcon
constructor:
import 'package:flutter/material.dart';
class MyCustomIcons {
MyCustomIcons._(); // Private constructor
static const IconData home = IconData(0xe900, fontFamily: 'MyCustomIcons');
static const IconData settings = IconData(0xe901, fontFamily: 'MyCustomIcons');
// ... map other icons using their Unicode code points from the font
}
// Usage:
Icon(MyCustomIcons.home, size: 24.0, color: Colors.red);
CustomPaint
(For Complex, Unique Shapes)
- Vector Path Translation: If the icon is a highly custom, unique illustration that can't be easily exported as a simple SVG or is part of a larger complex graphic, you might need to translate its vector paths from Figma into a
Path
object usingCustomPainter
. This is the most granular level of icon replication. - Avoid Rasterized Icons: Unless an icon is inherently a photograph or complex raster image, never export icons as PNG or JPG as they will pixelate when scaled or viewed on high-density screens.
Self-Correction and Learning from Mistakes
Why it Matters: The best way to improve is to reflect on what went wrong and why.
Actionable Advice
- Document Challenges: When you encounter a particularly tricky replication, document the problem, the Figma details, and how you eventually solved it. This builds your knowledge base.
- Refactor Regularly: As you learn new Flutter techniques or discover more efficient ways to structure your UI, don't be afraid to refactor existing code to apply these improvements.
- Stay Updated: Flutter and Figma are constantly evolving. Keep an eye on new features, widgets, and best practices that can make your replication process smoother.
To bridge the gap between theory and practice, we’ll build a complete Flutter application that replicates the three distinct UI designs shown below. I got this image from Envato. This project isn't just about copying, it's a practical demonstration of every principle we've discussed, from dissecting Figma's "Inspect" panel values to implementing a robust, scalable, and pixel-perfect component structure.

What we’ll build:
- A Smart Home Dashboard: Focusing on layout, BoxDecoration, and clean information hierarchy.
- A Music Player UI: Highlighting the use of Stack for complex overlapping layouts and custom list items.
- An Apartment Booking App: Demonstrating PageView for creating interactive carousels and a custom bottom navigation bar with a FloatingActionButton.
This hands-on example will solidify your understanding and serve as a reference for your own projects. Let's dive into the code.
Project Overview
This project is built following the core principles of a scalable and maintainable Flutter application. We are not just replicating the UI, we are architecting it correctly.
Guide
- Componentization: Every logical piece of the UI (a header, a card, a button) is isolated into its own widget file. This makes the code readable, reusable, and easy to test.
- Separation of Concerns: "Screen" files are responsible for the overall layout and state management. "Widget" files are responsible only for their own appearance and internal logic.
- Centralized Design System: All colors, dimensions, and text styles are defined in central utility files (
utils/
), mirroring the concept of "Design Tokens" in Figma.
Final Project Directory Structure
This is the clean, organized file structure we will build. It is the professional standard for Flutter projects.
lib/
├── main.dart
|
├── models/
│ └── track_model.dart
|
├── screens/
│ ├── home_dashboard_screen.dart
│ ├── smart_home_screen.dart
│ ├── music_player_screen.dart
│ └── apartment_booking_screen.dart
|
├── utils/
│ ├── app_colors.dart
│ ├── app_dimensions.dart
│ └── app_styles.dart
|
└── widgets/
├── app_mockup_frame.dart
├── smart_home/
│ ├── power_usage_card.dart
│ ├── remote_access_card.dart
│ ├── room_card.dart
│ └── smart_home_header.dart
├── music_player/
│ ├── artist_card.dart
│ ├── music_player_header.dart
│ ├── track_list.dart
│ └── track_list_item.dart
└── apartment_booking/
├── apartment_card.dart
├── apartment_carousel.dart
├── booking_bottom_nav.dart
├── booking_header.dart
└── booking_search.dart
Step-by-Step Code Implementation
Here is the complete code for each file, with explanations.
1. `pubspec.yaml
First, ensure your pubspec.yaml
includes the google_fonts
package to match the typography.
name: figma_replication_project
description: A new Flutter project.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
google_fonts: ^6.1.0 # For high-quality, custom fonts
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true
2. Utilities (lib/utils/
`)
This directory centralizes our design system, perfectly demonstrating the "Design Tokens" and "Consistent Spacing" principles from your article.
'package:flutter/material.dart';
// Centralizes all application colors for consistency and easy theming.
AppColors {
static const Color background = Color(0xFFFFD1B5);
static const Color primaryBlue = Color(0xFF3D82F8);
static const Color lightBlue = Color(0xFFD2E2FF);
static const Color textDark = Color(0xFF2E3A59);
static const Color textLight = Color(0xFF8F9BB3);
static const Color cardBackground = Colors.white;
}
// Centralizes all spacing and sizing values to maintain a consistent visual rhythm.
AppDimensions {
static const double spacingXXSmall = 4.0;
static const double spacingXSmall = 8.0;
static const double spacingSmall = 12.0;
static const double spacingMedium = 16.0;
static const double spacingLarge = 24.0;
static const double spacingXLarge = 32.0;
static const double borderRadiusSmall = 8.0;
static const double borderRadiusMedium = 16.0;
static const double borderRadiusLarge = 24.0;
static const double mockupWidth = 320.0;
static const double mockupHeight = 680.0;
}
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'app_colors.dart';
import 'app_dimensions.dart';
// Centralizes text styles and common decoration elements like shadows.
// This ensures absolute fidelity to the Figma typography specs.
class AppStyles {
// Base text style using a specific Google Font for a modern look.
static TextStyle get _baseTextStyle => GoogleFonts.poppins(
color: AppColors.textDark,
);
// Specific, named text styles that correspond to the Figma design.
static TextStyle h1 = _baseTextStyle.copyWith(fontSize: 22, fontWeight: FontWeight.w600);
static TextStyle h2 = _baseTextStyle.copyWith(fontSize: 18, fontWeight: FontWeight.w600);
static TextStyle bodyText = _baseTextStyle.copyWith(fontSize: 14, fontWeight: FontWeight.w500);
static TextStyle subtitle = _baseTextStyle.copyWith(fontSize: 12, color: AppColors.textLight, fontWeight: FontWeight.normal);
static TextStyle buttonText = _baseTextStyle.copyWith(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w600);
// Reusable box shadow, matching the Figma properties exactly.
static BoxShadow cardShadow = BoxShadow(
color: AppColors.primaryBlue.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
);
}
3. Model (lib/models/
)
This holds the data structure for our application's content, separating data from UI.
// Represents the data structure for a single music track.
class Track {
final String title;
final String duration;
final bool isPlaying;
Track({required this.title, required this.duration, this.isPlaying = false});
}
// Mock data for the music player screen.
final List<Track> topTracks = [
Track(title: 'Old Town Road', duration: '3:41'),
Track(title: 'I Don\'t Care', duration: '4:35', isPlaying: true),
Track(title: 'Dancing With A Stranger', duration: '3:12'),
Track(title: 'Sweet But Psycho', duration: '4:07'),
Track(title: 'If I Can\'t Have You', duration: '4:07'),
];
4. Reusable Widgets (lib/widgets/
)
This is the core of our component-based architecture.
Generic Widget
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:flutter/material.dart';
// A wrapper widget to create the "phone" frame around each UI design.
class AppMockupFrame extends StatelessWidget {
final Widget child;
const AppMockupFrame({Key? key, required this.child}) : super(key: key);
Widget build(BuildContext context) {
return Container(
width: AppDimensions.mockupWidth,
height: AppDimensions.mockupHeight,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusLarge),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 30,
offset: const Offset(0, 10),
),
],
border: Border.all(color: Colors.white.withOpacity(0.5), width: 2),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusLarge - 2),
child: child,
),
);
}
}
Smart Home Widgets (lib/widgets/smart_home/
)
Each file here is a self-contained component for the Smart Home screen.
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
class SmartHomeHeader extends StatelessWidget {
const SmartHomeHeader({Key? key}) : super(key: key);
// ... (code from previous answer)
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
class PowerUsageCard extends StatelessWidget {
const PowerUsageCard({Key? key}) : super(key: key);
// ... (code from previous answer)
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
class RoomCard extends StatelessWidget {
final String roomName;
final String deviceCount;
final Color color;
final Color textColor;
const RoomCard({
Key? key,
required this.roomName,
required this.deviceCount,
required this.color,
required this.textColor,
}) : super(key: key);
// ... (code from previous answer)
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
class RemoteAccessCard extends StatelessWidget {
const RemoteAccessCard({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimensions.spacingMedium,
vertical: AppDimensions.spacingSmall,
),
decoration: BoxDecoration(
color: AppColors.primaryBlue,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusMedium),
),
child: Row(
children: [
const Icon(Icons.info_outline, color: Colors.white, size: 20),
const SizedBox(width: AppDimensions.spacingSmall),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quick Remote Access',
style: AppStyles.bodyText.copyWith(color: Colors.white)),
Text(
'Click here to connect with your phone.',
style: AppStyles.subtitle
.copyWith(color: Colors.white.withOpacity(0.8), fontSize: 10),
),
],
),
),
],
),
);
}
}
Music Player Widgets (lib/widgets/music_player/
)
These widgets are the building blocks for the Music Player UI.
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
// This widget creates the distinctive blue, curved header.
// It uses SafeArea to ensure content isn't obscured by system UI (like notches).
class MusicPlayerHeader extends StatelessWidget {
const MusicPlayerHeader({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Container(
height: 260,
decoration: const BoxDecoration(
color: AppColors.primaryBlue,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(50),
bottomRight: Radius.circular(50),
),
),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.all(AppDimensions.spacingLarge),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.arrow_back, color: Colors.white),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.lightBlue.withOpacity(0.5),
border: Border.all(color: Colors.white, width: 1.5),
),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
// The floating card that displays artist information.
// It uses the centralized AppStyles.cardShadow for a consistent shadow effect.
class ArtistCard extends StatelessWidget {
const ArtistCard({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(AppDimensions.spacingMedium),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusMedium),
boxShadow: [AppStyles.cardShadow],
),
child: Row(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: AppColors.lightBlue,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusSmall),
),
),
const SizedBox(width: AppDimensions.spacingMedium),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Antonia Berger', style: AppStyles.h2),
Text('Hip Hop Artist', style: AppStyles.subtitle),
Text('antonia.com', style: AppStyles.subtitle.copyWith(color: AppColors.primaryBlue)),
],
)
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/models/track_model.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
// Represents a single row in the music track list.
// It conditionally renders its UI based on the `track.isPlaying` property.
class TrackListItem extends StatelessWidget {
final Track track;
const TrackListItem({Key? key, required this.track}) : super(key: key);
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: AppDimensions.spacingSmall),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: track.isPlaying ? AppColors.primaryBlue : Colors.transparent,
shape: BoxShape.circle,
border: Border.all(color: track.isPlaying ? AppColors.primaryBlue : AppColors.textLight.withOpacity(0.5), width: 1.5),
),
child: Icon(
track.isPlaying ? Icons.pause : Icons.play_arrow,
color: track.isPlaying ? Colors.white : AppColors.textDark,
size: 20,
),
),
const SizedBox(width: AppDimensions.spacingMedium),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(track.title, style: AppStyles.bodyText),
if (track.isPlaying)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: SizedBox(
height: 2,
child: LinearProgressIndicator(
value: 0.4,
backgroundColor: AppColors.lightBlue,
valueColor: const AlwaysStoppedAnimation<Color>(AppColors.primaryBlue),
),
),
),
],
),
),
Text(track.duration, style: AppStyles.subtitle),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/models/track_model.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
import 'package:figma_replication_project/widgets/music_player/track_list_item.dart';
// This widget builds the scrollable list of tracks.
// It maps the mock data from `topTracks` to `TrackListItem` widgets.
class TrackList extends StatelessWidget {
const TrackList({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.fromLTRB(
AppDimensions.spacingLarge,
AppDimensions.spacingLarge,
AppDimensions.spacingLarge,
0,
),
children: [
Text('Top Tracks', style: AppStyles.h2),
const SizedBox(height: AppDimensions.spacingMedium),
// Using the spread operator (...) to add all items from the list.
...topTracks.map((track) => TrackListItem(track: track)).toList(),
],
);
}
}
Apartment Booking Widgets (lib/widgets/apartment_booking/
)
These widgets are the building blocks for the Apartment Booking UI.
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
// The header section for the apartment booking screen.
class BookingHeader extends StatelessWidget {
const BookingHeader({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(
AppDimensions.spacingLarge,
AppDimensions.spacingMedium,
AppDimensions.spacingLarge,
0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.arrow_back, color: AppColors.textDark),
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.lightBlue.withOpacity(0.5),
border: Border.all(color: AppColors.primaryBlue, width: 1.5),
),
),
],
),
const SizedBox(height: AppDimensions.spacingMedium),
Text('Discover & book', style: AppStyles.h1),
Text('unique apartments', style: AppStyles.h1),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
// The search bar component with a suggestion chip and a search button.
class BookingSearch extends StatelessWidget {
const BookingSearch({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppDimensions.spacingLarge),
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimensions.spacingMedium,
vertical: AppDimensions.spacingSmall,
),
decoration: BoxDecoration(
color: AppColors.primaryBlue,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: Text(
'TRY "COPENHAGEN"',
style: AppStyles.buttonText.copyWith(fontSize: 12),
),
),
),
),
const SizedBox(width: AppDimensions.spacingSmall),
Container(
padding: const EdgeInsets.all(AppDimensions.spacingSmall),
decoration: const BoxDecoration(
color: AppColors.primaryBlue,
shape: BoxShape.circle,
),
child: const Icon(Icons.search, color: Colors.white, size: 24),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/utils/app_styles.dart';
// A single card representing an apartment in the carousel.
// It uses a Stack to position the text and the send button.
class ApartmentCard extends StatelessWidget {
const ApartmentCard({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColors.lightBlue,
borderRadius: BorderRadius.circular(AppDimensions.borderRadiusLarge),
boxShadow: [AppStyles.cardShadow],
),
child: Stack(
children: [
Positioned(
left: AppDimensions.spacingMedium,
bottom: AppDimensions.spacingMedium,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Tidy Minimal', style: AppStyles.h2),
Text('Berlin, Germany', style: AppStyles.subtitle),
],
),
),
Positioned(
right: AppDimensions.spacingMedium,
bottom: AppDimensions.spacingMedium,
child: Container(
padding: const EdgeInsets.all(AppDimensions.spacingSmall),
decoration: const BoxDecoration(
color: AppColors.primaryBlue,
shape: BoxShape.circle,
),
child: const Icon(Icons.send, color: Colors.white, size: 20, semanticLabel: 'Send'),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/widgets/apartment_booking/apartment_card.dart';
// This widget manages the PageView for the apartment cards.
// It's a StatelessWidget because it receives its state (controller, currentPage)
// from the parent StatefulWidget.
class ApartmentCarousel extends StatelessWidget {
final PageController pageController;
final int currentPage;
const ApartmentCarousel({
Key? key,
required this.pageController,
required this.currentPage,
}) : super(key: key);
Widget build(BuildContext context) {
return SizedBox(
height: 320,
child: PageView.builder(
controller: pageController,
itemCount: 3,
itemBuilder: (context, index) {
// AnimatedContainer creates the subtle scaling effect when a card is active.
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.only(
right: AppDimensions.spacingSmall,
top: currentPage == index ? 0 : 20,
bottom: currentPage == index ? 20 : 0,
),
child: const ApartmentCard(),
);
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
// The custom bottom navigation bar with a notch for the FloatingActionButton.
class BookingBottomNav extends StatelessWidget {
const BookingBottomNav({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return BottomAppBar(
shape: const CircularNotchedRectangle(),
notchMargin: 8.0,
color: AppColors.cardBackground,
surfaceTintColor: AppColors.cardBackground,
elevation: 20,
shadowColor: Colors.black.withOpacity(0.05),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(Icons.home_outlined, color: AppColors.textLight, size: 28),
Icon(Icons.search, color: AppColors.textLight, size: 28),
SizedBox(width: 48), // The placeholder space for the FAB
Icon(Icons.favorite_border, color: AppColors.textLight, size: 28),
Icon(Icons.person_outline, color: AppColors.textLight, size: 28),
],
),
);
}
}
5. Screens (lib/screens/
)
These files now act as clean, readable blueprints. They compose the smaller widgets to build the final UI.
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/widgets/smart_home/power_usage_card.dart';
import 'package:figma_replication_project/widgets/smart_home/remote_access_card.dart';
import 'package:figma_replication_project/widgets/smart_home/room_card.dart';
import 'package:figma_replication_project/widgets/smart_home/smart_home_header.dart';
// This screen composes various "smart_home" widgets to build the UI.
// Notice how readable this is compared to having all the code in one file.
class SmartHomeScreen extends StatelessWidget {
const SmartHomeScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.cardBackground,
body: Padding(
padding: const EdgeInsets.all(AppDimensions.spacingLarge),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SmartHomeHeader(),
const SizedBox(height: AppDimensions.spacingXLarge),
const PowerUsageCard(),
const SizedBox(height: AppDimensions.spacingLarge),
const Row(
children: [
Expanded(
child: RoomCard(
roomName: 'Living Room',
deviceCount: '9 Active Devices',
color: AppColors.lightBlue,
textColor: AppColors.textDark,
),
),
SizedBox(width: AppDimensions.spacingMedium),
Expanded(
child: RoomCard(
roomName: 'Bathroom',
deviceCount: '1 Active Device',
color: AppColors.primaryBlue,
textColor: Colors.white,
),
),
],
),
const Spacer(),
const RemoteAccessCard(),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/widgets/music_player/artist_card.dart';
import 'package:figma_replication_project/widgets/music_player/music_player_header.dart';
import 'package:figma_replication_project/widgets/music_player/track_list.dart';
// This screen uses a Stack to create the overlapping UI effect,
// a perfect demonstration of the "Deconstruct Layouts with Stack" principle.
class MusicPlayerScreen extends StatelessWidget {
const MusicPlayerScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.cardBackground,
body: Stack(
children: [
const Positioned.fill(top: 220, child: TrackList()),
const MusicPlayerHeader(),
const Positioned(
top: 120,
left: AppDimensions.spacingLarge,
right: AppDimensions.spacingLarge,
child: ArtistCard(),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/widgets/apartment_booking/apartment_carousel.dart';
import 'package:figma_replication_project/widgets/apartment_booking/booking_bottom_nav.dart';
import 'package:figma_replication_project/widgets/apartment_booking/booking_header.dart';
import 'package:figma_replication_project/widgets/apartment_booking/booking_search.dart';
// This is a StatefulWidget because it needs to manage the state of the
// PageController and the current active page index for the carousel animation.
class ApartmentBookingScreen extends StatefulWidget {
const ApartmentBookingScreen({Key? key}) : super(key: key);
State<ApartmentBookingScreen> createState() => _ApartmentBookingScreenState();
}
class _ApartmentBookingScreenState extends State<ApartmentBookingScreen> {
final PageController _pageController = PageController(viewportFraction: 0.85);
int _currentPage = 0;
void initState() {
super.initState();
// Listen to page changes to update the UI for the scaling effect.
_pageController.addListener(() {
if (_pageController.page?.round() != _currentPage) {
setState(() {
_currentPage = _pageController.page!.round();
});
}
});
}
void dispose() {
_pageController.dispose(); // Always dispose controllers to prevent memory leaks.
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.cardBackground,
body: SafeArea(
bottom: false,
child: Column(
children: [
const BookingHeader(),
const SizedBox(height: AppDimensions.spacingMedium),
const BookingSearch(),
const SizedBox(height: AppDimensions.spacingLarge),
// The carousel is passed the controller and current page state.
ApartmentCarousel(
pageController: _pageController,
currentPage: _currentPage,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: AppColors.primaryBlue,
shape: const CircleBorder(),
elevation: 4.0,
child: const Icon(Icons.add, color: Colors.white, size: 32),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: const BookingBottomNav(),
);
}
}
6. Application Entry Point
Finally, the main.dart
and home_dashboard_screen.dart
tie everything together.
import 'package:flutter/material.dart';
import 'package:figma_replication_project/screens/apartment_booking_screen.dart';
import 'package:figma_replication_project/screens/music_player_screen.dart';
import 'package:figma_replication_project/screens/smart_home_screen.dart';
import 'package:figma_replication_project/utils/app_dimensions.dart';
import 'package:figma_replication_project/widgets/app_mockup_frame.dart';
// This is the main screen that displays the three mockups.
// It uses a LayoutBuilder to create a responsive layout, demonstrating
// the "Embrace Constraints and Responsiveness" principle.
class HomeDashboardScreen extends StatelessWidget {
const HomeDashboardScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: AppDimensions.spacingXLarge),
child: LayoutBuilder(
builder: (context, constraints) {
bool isWide = constraints.maxWidth > (AppDimensions.mockupWidth * 3 + 100);
// Show in a Row on wide screens, and a Column on narrow screens.
return isWide
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AppMockupFrame(child: SmartHomeScreen()),
SizedBox(width: AppDimensions.spacingXLarge),
AppMockupFrame(child: MusicPlayerScreen()),
SizedBox(width: AppDimensions.spacingXLarge),
AppMockupFrame(child: ApartmentBookingScreen()),
],
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const AppMockupFrame(child: SmartHomeScreen()),
const SizedBox(height: AppDimensions.spacingXLarge),
const AppMockupFrame(child: MusicPlayerScreen()),
const SizedBox(height: AppDimensions.spacingXLarge),
const AppMockupFrame(child: ApartmentBookingScreen()),
],
);
},
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:figma_replication_project/screens/home_dashboard_screen.dart';
import 'package:figma_replication_project/utils/app_colors.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
title: 'Figma to Flutter Replication',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: AppColors.primaryBlue,
scaffoldBackgroundColor: AppColors.background,
useMaterial3: true,
),
home: const HomeDashboardScreen(),
);
}
}
Conclusion
Replicating Figma designs in Flutter with pixel-perfect accuracy is a skill developed through deliberate practice and an unwavering commitment to detail. It requires more than just understanding Flutter widgets. It demands a deep appreciation for the nuances of design, a systematic approach to breaking down complex layouts, and a relentless pursuit of visual fidelity. By internalizing these practices, you won't just be writing code, you'll be sculpting user interfaces that truly honor the designer's vision.