Your first Flutter app

1. Introduction

What you'll build

Flutter is Google's UI toolkit for building applications for mobile, web, and desktop from a single codebase. In this codelab, you collaborate with Google Antigravity to build a responsive Flutter application that generates random, cool-sounding names:

The application generates word pairings such as "newstay", "lightstream", "mainbrake", or "graypine". The user can request the next name, favorite the current one, and review the list of favorited names on a separate page. The app adapts automatically to different screen sizes.

Completed Namer app showing the random word card, Like and Next buttons, and responsive navigation rail

Your learning journey

This codelab takes you through building the Namer app step by step:

  1. Set up your Flutter environment - Configure the Flutter SDK and Google Antigravity for your target platform.
  2. Create a project - Prompt Antigravity to scaffold a new Flutter project with english_words, provider, and material_ui.
  3. Add a button - Connect a Next button to MyAppState to generate new word pairs and experience Stateful Hot Reload.
  4. Make the app prettier - Extract a custom BigCard widget styled with the app's Material Theme, add screen-reader accessibility, and center the UI.
  5. Add functionality - Track favorited word pairs in MyAppState and add a toggleable Like button with a heart icon.
  6. Add navigation rail - Convert MyHomePage into a StatefulWidget with a responsive NavigationRail driven by LayoutBuilder.
  7. Add a new page - Build a scrollable FavoritesPage using ListView and ListTile to display saved word pairs.

What you'll learn

  • How to collaborate with Google Antigravity to iteratively build and customize a responsive Flutter application.
  • How the basics of Flutter widgets, StatelessWidget, and StatefulWidget work.
  • How to create and compose layouts in Flutter using Column, Row, Center, Expanded, and ListView.
  • How to connect user interactions (such as button presses) to app-wide state with ChangeNotifier and provider.
  • How to achieve a consistent look and feel using Theme, ColorScheme, and TextTheme from package:material_ui/material_ui.dart.
  • How to make your app responsive to different screen sizes with LayoutBuilder.

Prerequisites

  • No prior Flutter experience is required. Basic familiarity with programming concepts (such as variables, functions, and classes) is helpful.

2. Set up your Flutter environment

Editor

This codelab uses Google Antigravity as your AI-first development environment. In each step, you prompt Antigravity to generate or evolve your app's components, review the key Flutter concepts in the generated code, and optionally explore creative variations.

Choose a development target

Flutter is a multi-platform toolkit. Your app can run on any of the following operating systems:

  • iOS
  • Android
  • Windows
  • macOS
  • Linux
  • web

However, it's common practice to choose a single operating system for which you primarily develop. This is your "development target"—the operating system that your app runs on during development.

A development laptop connected to a target mobile phone with a cable

For example, suppose you're using a Windows laptop to develop a Flutter app. If you choose Android as your development target, you typically attach an Android device to your Windows laptop with a USB cable (or launch an Android emulator), and your app-in-development runs on that target device. Alternatively, you can choose Windows as the development target, which means your app-in-development runs as a desktop Windows app alongside your editor.

Make your choice before continuing. Remember: you can always run your app on other operating systems later. Having a clear development target in mind makes the next step smoother.

Install Flutter

The most up-to-date instructions on how to install the Flutter SDK are always at docs.flutter.dev.

The instructions on the Flutter website cover installing the SDK itself, development target tools, and editor plugins. For this codelab, you only need to install the following software:

  1. Flutter SDK
  2. Google Antigravity with the official Dart and Flutter plugin enabled (available in the Plugin Marketplace under the Build with Google section).
  3. Compiler software for your chosen development target (for example, Visual Studio to target Windows or Xcode to target macOS or iOS).

Verify your setup

To verify that your environment is set up for your chosen development target, run flutter doctor:

flutter doctor

If there are issues listed in the output that impact your chosen development target, run flutter doctor -v for detailed troubleshooting steps, or reach out to the Flutter community.

In the next step, you'll create your first Flutter project.

3. Create a project

Prompt Antigravity to create the project

  1. Launch Google Antigravity and open an empty directory where you'd like to build your project (choose a path that doesn't require elevated privileges or contain spaces, such as a folder in your home directory or C:\src\).
  2. In the Antigravity agent chat panel, send the following prompt to create the Flutter project, add the required packages, and wire up the initial app scaffold:
Create a new empty Flutter project named `namer_app` for a random name generator app. Add the `english_words`, `provider`, and `material_ui` packages. Update `lib/main.dart` (importing `package:english_words/english_words.dart`, `package:material_ui/material_ui.dart`, and `package:provider/provider.dart`) to define `MyApp` (`StatelessWidget` wrapping a `MaterialApp` titled `'Namer App'` with `ColorScheme.fromSeed(seedColor: Colors.deepOrange)` inside a `ChangeNotifierProvider` for `MyAppState`), `MyAppState extends ChangeNotifier` holding `var current = WordPair.random()`, and `MyHomePage` (`StatelessWidget` watching `MyAppState` and returning a `Scaffold` with a `Column` containing `Text('A random idea:')` and `Text(appState.current.asLowerCase)`).

Understanding the generated code

Antigravity runs flutter create --empty namer_app to scaffold the project, adds english_words, provider, and material_ui to pubspec.yaml, and sets up lib/main.dart.

Application entry point and MyApp

At the top of lib/main.dart, the main() function instructs Flutter to run the root widget defined in MyApp:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

The MyApp class extends StatelessWidget. Widgets are the building blocks of every Flutter app—even the app itself is a widget. MyApp provides app-wide state using ChangeNotifierProvider, sets the app title, defines the visual ThemeData using a Material 3 seed color, and sets MyHomePage as the starting home widget.

App-wide state with ChangeNotifier

Next, MyAppState defines the data the application needs to function:

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}
  • Right now, MyAppState contains a single variable holding the current random WordPair (from the english_words package).
  • MyAppState extends ChangeNotifier, which means it can notify other widgets whenever its data changes.
  • Because MyApp wraps the widget tree in a ChangeNotifierProvider, any widget in the app can subscribe to MyAppState.

MyApp providing MyAppState to MyHomePage and other widgets in the widget tree

Building the UI in MyHomePage

Finally, MyHomePage renders the current word pair on screen:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [Text('A random idea:'), Text(appState.current.asLowerCase)],
      ),
    );
  }
}
  1. Every widget defines a build() method that Flutter calls automatically whenever the widget's dependencies change.
  2. MyHomePage tracks changes to the app's current state using context.watch().
  3. Every build() method returns a widget or a nested tree of widgets. Here, the top-level widget is Scaffold, which provides a standard Material layout structure.
  4. Column is a basic layout widget that arranges its children vertically from top to bottom.

Run and test the app

Run the app on your chosen development target to verify everything builds and launches cleanly. Your app displays the initial prompt text and a single random word pair in the upper-left corner:

Initial Namer app running with the random idea text at the top left

4. Add a button

In this step, you'll update the header text and add a Next button that generates a new random word pairing whenever pressed.

Prompt Antigravity to add the Next button

Send the following prompt in Antigravity:

In `lib/main.dart`, add a `getNext()` method to `MyAppState` that reassigns `current = WordPair.random()` and calls `notifyListeners()`. In `MyHomePage`, update the first `Text` widget to `'A random AWESOME idea:'` and add an `ElevatedButton` labeled `'Next'` at the bottom of the `Column` whose `onPressed` callback calls `appState.getNext()`.

Understanding the generated code

Mutating state and notifying listeners

In lib/main.dart, MyAppState now includes a getNext() method:

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }
}

The getNext() method reassigns current with a fresh random WordPair and calls notifyListeners() (a method inherited from ChangeNotifier) so that any widget watching MyAppState automatically rebuilds.

Connecting ElevatedButton to MyAppState

In MyHomePage, the Column now includes an ElevatedButton under the two Text widgets:

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(appState.current.asLowerCase),
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );

Notice how Flutter code makes heavy use of trailing commas after parameter lists (such as after child: Text('Next'), and ],). Trailing commas streamline adding or reordering parameters and instruct Dart's auto-formatter to place each item on its own line. For more details, see Code formatting.

Run and test the app

Save your changes (or trigger a Hot Reload if your app is already running). Notice how the text updates immediately to A random AWESOME idea: and the Next button appears without losing your current word pair—this is Flutter's Stateful Hot Reload at work.

Click the Next button several times to verify that a new random word pair is generated on every press.

5. Make the app prettier

Right now, the centerpiece of the app—the randomly generated pair of words—is small, off-center, and plain:

App showing small unstyled text and Next button in the top-left corner

In this step, you'll extract the word pair into a dedicated BigCard widget, style it with a rich Material Card and typography from your app's Theme, add screen-reader accessibility, and center the layout in the window:

Target design showing a centered primary-colored BigCard and the Next button

Prompt Antigravity to style the word card and center the layout

Send the following prompt in Antigravity:

In `lib/main.dart`, extract the word pair display into a `BigCard` `StatelessWidget` that accepts a required `final WordPair pair`. In `BigCard.build`, look up `final theme = Theme.of(context)` and define `style` as `theme.textTheme.displayMedium!.copyWith(color: theme.colorScheme.onPrimary)`, then return a `Card` with `color: theme.colorScheme.primary` wrapping a `Padding` (`EdgeInsets.all(20)`) and a `Text(pair.asLowerCase, style: style, semanticsLabel: "${pair.first} ${pair.second}")`. Finally, update `MyHomePage` to remove the `'A random AWESOME idea:'` text, center the `Column` vertically (`mainAxisAlignment: MainAxisAlignment.center`) inside a `Center` widget, and place a `SizedBox(height: 10)` between `BigCard(pair: pair)` and the `'Next'` `ElevatedButton`.

Understanding the generated code

Extracting BigCard and Composition over Inheritance

Having separate widgets for logical parts of your UI is one of the best ways to manage complexity in Flutter. Rather than passing the entire MyAppState into BigCard, BigCard only accepts the WordPair it needs:

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair});

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }
}

Styling with Theme, ColorScheme, and TextTheme

  • Theme.of(context) retrieves the app's current ThemeData.
  • Setting the card's color to theme.colorScheme.primary paints it with the most prominent color derived from your ColorScheme.fromSeed in MyApp. If you change the seed color in MyApp, BigCard and ElevatedButton smoothly interpolate to the new palette using Flutter's implicit animations:
  • theme.textTheme.displayMedium provides a large typographic style reserved for short, important display text. Calling copyWith(color: theme.colorScheme.onPrimary) returns a copy of that style using a high-contrast text color designed to be legible on the primary background.

Screen-reader accessibility with semanticsLabel

Flutter exposes text and interactive elements to screen readers such as TalkBack and VoiceOver by default. However, a compound word like cheaphead might confuse a screen reader into pronouncing the ph in the middle as f.

Visual compound word presentation compared with two-word screen reader pronunciation

By setting semanticsLabel: "${pair.first} ${pair.second}", Text preserves the visual simplicity of pair.asLowerCase while providing two separate words to screen readers. Learn more on Flutter's Accessibility documentation page.

Centering the UI in MyHomePage

In MyHomePage, the Column is centered both vertically and horizontally:

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                appState.getNext();
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}
  • mainAxisAlignment: MainAxisAlignment.center centers the children along the Column's vertical (main) axis.
  • Because a Column only takes as much horizontal width as its children need, wrapping the Column in a Center widget centers it horizontally inside the Scaffold.
  • SizedBox(height: 10) adds a 10-pixel vertical gap between BigCard and the ElevatedButton.

Run and test the app

Hot reload the app and verify that BigCard is centered on screen with the deep-orange primary background and large white text:

Namer app with centered primary-colored BigCard and Next button

6. Add functionality

Whenever the user clicks Next, the previous word pair disappears. In this step, you'll add a favorites list to MyAppState and place a Like button with a heart icon next to the Next button:

Namer app showing the Like button with a heart icon next to the Next button

Prompt Antigravity to add Favorites state and a Like button

Send the following prompt in Antigravity:

In `lib/main.dart`, add `var favorites = <WordPair>[]` and a `toggleFavorite()` method to `MyAppState` that removes `current` from `favorites` if already present or adds it otherwise, then calls `notifyListeners()`. In `MyHomePage.build`, choose `Icons.favorite` when `appState.favorites.contains(pair)` and `Icons.favorite_border` otherwise, and place an `ElevatedButton.icon` (calling `appState.toggleFavorite()`, with `icon: Icon(icon)` and `label: Text('Like')`), a `SizedBox(width: 10)`, and the `'Next'` `ElevatedButton` inside a horizontal `Row` with `mainAxisSize: MainAxisSize.min`.

Understanding the generated code

Managing favorites in MyAppState

MyAppState now tracks a list of favorited word pairs and exposes a method to toggle the current pair:

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}
  • favorites is initialized with [], using Dart generics to ensure the list can only ever contain WordPair objects.
  • toggleFavorite() removes current if it's already in favorites or adds it otherwise, then calls notifyListeners() so the UI updates immediately.

Horizontal button layout with Row and ElevatedButton.icon

In MyHomePage.build, the code selects the appropriate heart icon based on whether pair is in appState.favorites, and places both buttons inside a Row:

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                ElevatedButton.icon(
                  onPressed: () {
                    appState.toggleFavorite();
                  },
                  icon: Icon(icon),
                  label: Text('Like'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  • Row is the horizontal equivalent of Column. By default, a Row expands to fill all available horizontal width and aligns its children to the left. Setting mainAxisSize: MainAxisSize.min tells Row to shrink-wrap its children so the button pair stays centered under BigCard.
  • ElevatedButton.icon() is a convenient constructor that pairs an icon with a text label.

Run and test the app

Hot reload the app and tap Like on a few generated word pairs. Verify that the heart icon toggles between an outlined heart (Icons.favorite_border) and a solid heart (Icons.favorite):

In the next step, you'll add a navigation rail so users can switch to a dedicated screen to view their saved favorites.

7. Add navigation rail

To let users switch between the word generator and their saved favorites, you'll split MyHomePage into two widgets (MyHomePage and GeneratorPage), convert MyHomePage into a StatefulWidget that tracks the selected navigation index, and wrap the layout in a LayoutBuilder so the NavigationRail automatically expands its labels when the window is at least 600 logical pixels wide:

Namer app with a side NavigationRail and primaryContainer content area

Prompt Antigravity to add a responsive NavigationRail

Send the following prompt in Antigravity:

In `lib/main.dart`, extract the existing home content (without its `Scaffold`) into a `GeneratorPage` `StatelessWidget`, and convert `MyHomePage` into a `StatefulWidget` whose `_MyHomePageState` tracks `var selectedIndex = 0`. In `_MyHomePageState.build`, select `Widget page` using a `switch (selectedIndex)` that assigns `GeneratorPage()` for `0`, `Placeholder()` for `1`, and throws `UnimplementedError('no widget for $selectedIndex')` by default. Return a `LayoutBuilder` wrapping a `Scaffold` whose body is a `Row` containing a `SafeArea` with a `NavigationRail` (`extended: constraints.maxWidth >= 600`, `'Home'` and `'Favorites'` destinations, `selectedIndex: selectedIndex`, and `onDestinationSelected` updating `selectedIndex` inside `setState`) followed by an `Expanded` `Container` colored with `Theme.of(context).colorScheme.primaryContainer` displaying `page`.

Understanding the generated code

StatelessWidget versus StatefulWidget

Until now, MyAppState covered all of your state needs, so every widget was a StatelessWidget. However, the currently selected tab (selectedIndex) is UI state that only matters to MyHomePage.

App-wide state in MyAppState contrasted with widget-local state inside a StatefulWidget

Converting MyHomePage into a StatefulWidget creates a companion private state class, _MyHomePageState, which extends State and manages its own mutable selectedIndex variable:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

Selecting the active page and setState

Inside _MyHomePageState.build, a switch statement assigns the active screen to page based on selectedIndex:

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
      case 1:
        page = Placeholder();
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }
  • Because FavoritesPage isn't built yet, case 1 uses Placeholder(), a handy built-in widget that draws a crossed rectangle to mark unfinished UI:

Placeholder widget drawing a crossed rectangle in the content area

  • Following the fail-fast principle, the default branch throws an UnimplementedError if selectedIndex is neither 0 nor 1. If you ever add a third destination to the navigation rail and forget to update the switch, the app immediately exposes the bug during development.

Responsive layout with LayoutBuilder, SafeArea, and Expanded

The build method returns a LayoutBuilder whose callback receives the parent's BoxConstraints whenever the window size or orientation changes:

    return LayoutBuilder(
      builder: (context, constraints) {
        return Scaffold(
          body: Row(
            children: [
              SafeArea(
                child: NavigationRail(
                  extended: constraints.maxWidth >= 600,
                  destinations: [
                    NavigationRailDestination(
                      icon: Icon(Icons.home),
                      label: Text('Home'),
                    ),
                    NavigationRailDestination(
                      icon: Icon(Icons.favorite),
                      label: Text('Favorites'),
                    ),
                  ],
                  selectedIndex: selectedIndex,
                  onDestinationSelected: (value) {
                    setState(() {
                      selectedIndex = value;
                    });
                  },
                ),
              ),
              Expanded(
                child: Container(
                  color: Theme.of(context).colorScheme.primaryContainer,
                  child: page,
                ),
              ),
            ],
          ),
        );
      },
    );
  • SafeArea prevents the NavigationRail buttons from being obscured by a mobile status bar or hardware notch.
  • extended: constraints.maxWidth >= 600 automatically expands the rail to show text labels next to the icons when the window is at least 600 logical pixels wide:

Namer app with an extended NavigationRail showing Home and Favorites labels

  • When the user clicks a destination, onDestinationSelected updates selectedIndex inside a setState(() { ... }) call, which tells Flutter to rebuild _MyHomePageState with the new tab selected.
  • Expanded is "greedy": while SafeArea takes only the horizontal slice the NavigationRail needs on the left, Expanded fills all remaining horizontal space with the primaryContainer-colored Container holding page.

Run and test the app

Hot reload the app and test the new navigation rail:

  1. Click Favorites (the heart icon) in the NavigationRail to switch to the Placeholder screen, then click Home to return to GeneratorPage.
  2. Drag the edge of your application window wider and narrower across the 600-pixel threshold to watch the NavigationRail smoothly expand and collapse its text labels:

8. Add a new page

In this final construction step, you'll replace the Placeholder widget with a FavoritesPage that displays all the word pairs the user has liked.

Prompt Antigravity to build the FavoritesPage

Send the following prompt in Antigravity:

In `lib/main.dart`, create a `FavoritesPage` `StatelessWidget` that watches `MyAppState`. If `appState.favorites.isEmpty`, return a `Center` widget with `Text('No favorites yet.')`; otherwise return a `ListView` starting with a `Padding` (`EdgeInsets.all(20)`) showing `Text('You have ${appState.favorites.length} favorites:')`, followed by a `ListTile` (`leading: Icon(Icons.favorite)` and `title: Text(pair.asLowerCase)`) for each `pair` in `appState.favorites`. Update `_MyHomePageState` so `case 1` in the `switch (selectedIndex)` statement displays `FavoritesPage()` instead of `Placeholder()`.

Understanding the generated code

Scrollable lists with ListView, ListTile, and collection for

In lib/main.dart, FavoritesPage watches MyAppState and renders either an empty-state message or a scrollable ListView:

class FavoritesPage extends StatelessWidget {
  const FavoritesPage({super.key});

  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have ${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}
  • When appState.favorites.isEmpty is true, the page returns a centered No favorites yet. label so the user gets clear feedback.
  • ListView is a Column that automatically scrolls when its contents exceed the available screen height.
  • Dart supports collection for loops directly inside list literals (children: [ ... ]), letting you generate a ListTile for every pair in appState.favorites declaratively.
  • ListTile provides a standard Material list row with a leading icon and title text.

Run and test the app

Hot reload the app and test the complete flow:

  1. Switch to the Favorites tab before liking any words to verify the centered No favorites yet. message appears.
  2. Return to Home, click Next and Like on several word pairs, and switch back to Favorites to see your saved list update automatically:

9. Congratulations

Congratulations! You collaborated with Google Antigravity to take a minimal Flutter scaffold and turn it into a responsive, Material 3 application that generates and saves random word pairs across platforms.

Completed Namer app showing the responsive navigation rail, word card, and favorites screen

What you've built

  • Collaborated with Google Antigravity to scaffold and iteratively evolve a Flutter application.
  • Composed Flutter UI layouts using Scaffold, Column, Row, Center, Padding, SizedBox, SafeArea, Expanded, and ListView.
  • Managed app-wide state with ChangeNotifier and provider, and widget-local state with StatefulWidget and setState().
  • Styled custom widgets with Theme, ColorScheme, and TextTheme using package:material_ui/material_ui.dart, and added semanticsLabel support for screen readers.
  • Built an adaptive NavigationRail layout using LayoutBuilder constraints.

Next steps

  • Experiment further with the app you built in this codelab by prompting Antigravity to add new features, animations, or layouts.
  • Explore the code of this advanced version of Namer App to see how you can add animated history lists, gradients, cross-fades, and more:

Learn more