Skip to content

Appetite UI Code

Ship an app that looks designed.

Appetite UI components in React Native, SwiftUI, Flutter and Jetpack Compose, with an App Starter and an agent kit. No Figma needed.

No stack yet? Start with React Native and Expo, where the App Starter lives.

The My work screen of the App Starter's Tasks flow: this week's progress, 12 of 23 done, a Today, Upcoming and Done segmented control, four task rows with checkboxes, badges and avatars, and a tab bar.The My work screen of the App Starter's Tasks flow: this week's progress, 12 of 23 done, a Today, Upcoming and Done segmented control, four task rows with checkboxes, badges and avatars, and a tab bar.

React Native: Segmented Control

// React Native
import { useState } from 'react';
import { SegmentedControl } from '@appetite-ui/react-native';

export function RangePicker() {
  const [range, setRange] = useState('Week');
  return (
    <SegmentedControl
      options={['Day', 'Week', 'Month']}
      value={range}
      onValueChange={setRange}
      accessibilityLabel="Range"
    />
  );
}

SwiftUI: Segmented Control

// SwiftUI (AppetiteSegmentedControl.swift copied into the app target)
import SwiftUI
import AppetiteCore

struct RangePicker: View {
    @State private var range = "Week"

    var body: some View {
        AppetiteSegmentedControl(options: ["Day", "Week", "Month"], value: range, accessibilityLabel: "Range") { range = $0 }
    }
}

Flutter: Segmented Control

// Flutter
import 'package:appetite_ui/appetite.dart';
import 'package:flutter/widgets.dart';

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

  @override
  State<RangePicker> createState() => _RangePickerState();
}

class _RangePickerState extends State<RangePicker> {
  String range = 'Week';

  @override
  Widget build(BuildContext context) {
    return SegmentedControl(
      options: const ['Day', 'Week', 'Month'],
      value: range,
      onValueChange: (v) => setState(() => range = v),
      semanticLabel: 'Range',
    );
  }
}

Compose: Segmented Control

// Jetpack Compose
@Composable
fun RangePicker() {
    var range by remember { mutableStateOf("Week") }
    AppetiteSegmentedControl(listOf("Day", "Week", "Month"), range, { range = it }, contentDescription = "Range")
}
  • 4

    Stacks: React Native, SwiftUI, Flutter and Jetpack Compose

  • 41

    Components in React Native

  • 15

    Core components in SwiftUI, Flutter and Compose

  • 58

    Expo SDK, React Native 0.88

  • 3

    App Starter flows: Tasks, Foodie and AI

Stacks

One folder per stack.

The same layout in every stack. React Native has every component. SwiftUI, Flutter and Compose start with the core set.

  • React Native with Expo

    Folder: react-native/

    Terminal
    cd starter
    npm install
    npx expo start

    41 components

  • SwiftUI

    Folder: swiftui/

    Xcode
    File > Add Package Dependencies > Add Local

    15 core components

  • Flutter

    Folder: flutter/

    Terminal
    flutter pub get

    15 core components

  • Jetpack Compose

    Folder: compose/

    build.gradle.kts
    implementation(project(":components"))

    15 core components

Agent kit

Your agent builds with the components.

Give Claude Code or Cursor the agent kit and a prompt. It builds the screen from Appetite UI components and tokens, with no hardcoded values.

Attach AGENTS.md and ask for the screen.

AGENTS.md points the agent to the kit's skills. Attach it and say what you want built.
A prompt with AGENTS.md from the agent kit attached: Build the My work screen with Appetite UI components.

It maps the screen to components.

Each part becomes an Appetite UI component or token, by its name in the Figma file.
The prompt, and under it what My work is made of: the filter is Segmented Control, a task row Checkbox, Badge and Avatar, the tab bar Navigation, the progress brand/base and the summary card bg/weak-50.

You get the finished screen.

My work, built from those components and tokens, in light and dark mode.
The finished My work screen: this week 12 of 23 done, a Today, Upcoming and Done segmented control, four task rows with checkboxes, badges and avatars, and the tab bar.

App Starter

Start from working flows.

Tasks, Foodie and AI flows on Expo Router, in light and dark mode. Open the App Starter on your phone with Expo Go.

Tasks. My work, the board, a task, a new task and the filters.

  • Tasks, My work: this week 12 of 23 done, a Today, Upcoming and Done segmented control, task rows with checkboxes, badges and avatars, an add button and the tab bar.Tasks, My work: this week 12 of 23 done, a Today, Upcoming and Done segmented control, task rows with checkboxes, badges and avatars, an add button and the tab bar.
    My work
  • Tasks, The Product board: a To do column of task cards with Mobile, Web and Marketing badges, due dates, comment counts and avatars, and an Add task button.Tasks, The Product board: a To do column of task cards with Mobile, Web and Marketing badges, due dates, comment counts and avatars, and an Add task button.
    Board
  • Tasks, A task, Onboarding redesign: status In progress, assignee, due date, priority and labels as rows, a description, two attachments and a comment field.Tasks, A task, Onboarding redesign: status In progress, assignee, due date, priority and labels as rows, a description, two attachments and a comment field.
    Task
  • Tasks, The filters sheet over the board: a Board, Table and Timeline control, status, priority and assignee chips, an Only overdue toggle, Clear and Show 3 results.Tasks, The filters sheet over the board: a Board, Table and Timeline control, status, priority and assignee chips, an Only overdue toggle, Clear and Show 3 results.
    Filters

Foodie. Home, a restaurant, a dish, the cart, checkout and the courier on the way.

  • Foodie, Foodie home: Delivery and Pickup, the delivery address, a search field, cuisine shortcuts, a Free delivery all week promotion and Fastest near you.Foodie, Foodie home: Delivery and Pickup, the delivery address, a search field, cuisine shortcuts, a Free delivery all week promotion and Fastest near you.
    Home
  • Foodie, Ramen Kitchen: a photo header, rating and delivery time, Popular, Ramen, Sushi and Drinks tabs, dishes with prices and add buttons, and a View cart bar.Foodie, Ramen Kitchen: a photo header, rating and delivery time, Popular, Ramen, Sushi and Drinks tabs, dishes with prices and add buttons, and a View cart bar.
    Restaurant
  • Foodie, Shoyu ramen in a sheet over its photo: the price, a size choice of Regular or Large, extras as toggles and an Add to cart button.Foodie, Shoyu ramen in a sheet over its photo: the price, a size choice of Regular or Large, extras as toggles and an Add to cart button.
    Dish
  • Foodie, Order tracking: a map with the courier route, Arriving in 12 minutes, a timeline of four order steps, the courier with message and call buttons, and the order.Foodie, Order tracking: a map with the courier route, Arriving in 12 minutes, a timeline of four order steps, the courier with message and call buttons, and the order.
    Tracking

AI. A new chat, an answer with a table and sources, a streaming reply, an attachment, an error and the history.

  • AI, Appetite AI new chat: Good morning, Maren, four suggestion chips and the Ask anything composer.AI, Appetite AI new chat: Good morning, Maren, four suggestion chips and the Ask anything composer.
    New chat
  • AI, An answer about churn with a three row table of plan figures, two source cards and copy, like, dislike and retry actions.AI, An answer about churn with a three row table of plan figures, two source cards and copy, like, dislike and retry actions.
    Thread
  • AI, A reply streaming in under a question, with the typing dots and a stop button in the composer.AI, A reply streaming in under a question, with the typing dots and a stop button in the composer.
    Generating
  • AI, An answer listing three renewal risks, and the composer with a PDF attached and a new question typed.AI, An answer listing three renewal risks, and the composer with a PDF attached and a new question typed.
    Attachment

Rebrand

Rebrand in one theme file.

Set the brand color, radius and font in one theme file. Every component and the App Starter follow.

The same My work screen rebranded to orange #CA3500: orange progress bar, checked task, add button and tab, text in Plus Jakarta Sans.The same My work screen rebranded to orange #CA3500: orange progress bar, checked task, add button and tab, text in Plus Jakarta Sans.

brand #CA3500font Plus Jakarta SansradiusScale 1

React Native: the brand

import type { AppetiteBrand } from '@appetite-ui/core';

export const brand: AppetiteBrand = {
  color: '#155dfc',
  radiusScale: 1, // radius/md is 8 at scale 1
  fonts: { body: 'Inter', heading: 'Inter Display' },
};
import type { AppetiteBrand } from '@appetite-ui/core';

export const brand: AppetiteBrand = {
  color: '#ca3500',
  radiusScale: 1, // radius/md is 8 at scale 1
  fonts: { body: 'Plus Jakarta Sans', heading: 'Plus Jakarta Sans' },
  fontFiles: {
    'Plus Jakarta Sans': {
      faces: {
        400: require('../../../assets/fonts/PlusJakartaSans-Regular.ttf'),
        500: require('../../../assets/fonts/PlusJakartaSans-Medium.ttf'),
        600: require('../../../assets/fonts/PlusJakartaSans-SemiBold.ttf'),
      },
      ascender: 1.038,
      descender: 0.222,
    },
  },
};
import type { AppetiteBrand } from '@appetite-ui/core';

export const brand: AppetiteBrand = {
  color: '#7c3aed',
  radiusScale: 1, // radius/md is 8 at scale 1
  fonts: { body: 'Inter', heading: 'Inter Display' },
};

SwiftUI: the brand

let defaultBrand = AppetiteBrand(
    color: "#155dfc",
    radiusScale: 1,
    bodyFont: "Inter",
    headingFont: "Inter Display"
)

ContentView()
    .appetiteTheme(brand: defaultBrand)
let orangeBrand = AppetiteBrand(
    color: "#ca3500",
    radiusScale: 1,
    bodyFont: "Plus Jakarta Sans",
    headingFont: "Plus Jakarta Sans",
    fontFiles: [
        "Plus Jakarta Sans": AppetiteBrandFontFamily(
            faces: [400: "PlusJakartaSans-Regular", 500: "PlusJakartaSans-Medium", 600: "PlusJakartaSans-SemiBold"],
            ascender: 1.038,
            descender: 0.222
        ),
    ]
)

ContentView()
    .appetiteTheme(brand: orangeBrand)
let violetBrand = AppetiteBrand(
    color: "#7c3aed",
    radiusScale: 1,
    bodyFont: "Inter",
    headingFont: "Inter Display"
)

ContentView()
    .appetiteTheme(brand: violetBrand)

Flutter: the brand

const defaultBrand = AppetiteBrand(
  color: '#155dfc',
  radiusScale: 1,
  fonts: AppetiteBrandFonts(body: 'Inter', heading: 'Inter Display'),
);

AppetiteScope(brand: defaultBrand, child: MyApp())
const orangeBrand = AppetiteBrand(
  color: '#ca3500',
  radiusScale: 1,
  fonts: AppetiteBrandFonts(body: 'Plus Jakarta Sans', heading: 'Plus Jakarta Sans'),
  fontWeights: {
    'Plus Jakarta Sans': {400, 500, 600},
  },
);

AppetiteScope(brand: orangeBrand, child: MyApp())
const violetBrand = AppetiteBrand(
  color: '#7c3aed',
  radiusScale: 1,
  fonts: AppetiteBrandFonts(body: 'Inter', heading: 'Inter Display'),
);

AppetiteScope(brand: violetBrand, child: MyApp())

Compose: the brand

val DefaultBrand = AppetiteBrand(
    color = "#155dfc",
    radiusScale = 1f,
    fonts = AppetiteBrandFonts(body = "Inter", heading = "Inter Display"),
)

AppetiteTheme(brand = DefaultBrand) { MyApp() }
val OrangeBrand = AppetiteBrand(
    color = "#ca3500",
    radiusScale = 1f,
    fonts = AppetiteBrandFonts(body = "Plus Jakarta Sans", heading = "Plus Jakarta Sans"),
    fontFamilies = mapOf(
        "Plus Jakarta Sans" to FontFamily(
            Font(R.font.plus_jakarta_sans_regular, FontWeight.W400),
            Font(R.font.plus_jakarta_sans_medium, FontWeight.W500),
            Font(R.font.plus_jakarta_sans_semibold, FontWeight.W600),
        ),
    ),
)

AppetiteTheme(brand = OrangeBrand) { MyApp() }
val VioletBrand = AppetiteBrand(
    color = "#7c3aed",
    radiusScale = 1f,
    fonts = AppetiteBrandFonts(body = "Inter", heading = "Inter Display"),
)

AppetiteTheme(brand = VioletBrand) { MyApp() }

With Figma

Same names as the Figma kit.

Variants in the Figma file are props in the code. If your designer works in Figma, both of you read one system.

A round photo avatar with a blue verified badgeA round photo avatar with a blue verified badge
A grey Label badge with a clock icon and a red count badgeA grey Label badge with a clock icon and a red count badge
A filled Pay button above an outlined Cancel buttonA filled Pay button above an outlined Cancel button
A checked checkbox in the brand blueA checked checkbox in the brand blue
Filter chips All, Unread, Starred and Shared, removable name chips, and chips with iconsFilter chips All, Unread, Starred and Shared, removable name chips, and chips with icons
An email input filled with hi@appetiteui.com and a hint line under itAn email input filled with hi@appetiteui.com and a hint line under it
A settings list: Personal details, Push notifications switched on, and App languageA settings list: Personal details, Push notifications switched on, and App language
A Confirm payment modal listing Northwind and a Visa card, with Pay and CancelA Confirm payment modal listing Northwind and a Visa card, with Pay and Cancel
A bottom tab bar with Home selected, History, Cards and PortfolioA bottom tab bar with Home selected, History, Cards and Portfolio
A selected radio button in the brand blueA selected radio button in the brand blue
A segmented control with All selected, Income and ExpensesA segmented control with All selected, Income and Expenses
Tabs All, Unread and Mentions, with All underlinedTabs All, Unread and Mentions, with All underlined
A toast with an info icon, a title, a line of text under it, a View link and a close buttonA toast with an info icon, a title, a line of text under it, a View link and a close button
A toggle switched on in the brand blueA toggle switched on in the brand blue
An Inbox top bar with a back button, a message count, and bookmark and search actionsAn Inbox top bar with a back button, a message count, and bookmark and search actions

Figma: Avatar

Docs for Avatar

Figma: Badge

Docs for Badge

Figma: Button

Docs for Button

Figma: Checkbox

Docs for Checkbox

Figma: Chip

Docs for Chip

Figma: Input

Docs for Input

Figma: List

Docs for List

Figma: Modal

Docs for Modal

Figma: Navigation

Docs for Navigation

Figma: Radio

Docs for Radio

Figma: Segmented Control

Docs for Segmented Control

Figma: Tab

Docs for Tab

Figma: Toast

Docs for Toast

Figma: Toggle

Docs for Toggle

Figma: Top Bars

Docs for Top Bars
React Native
Avatar
SwiftUI
AppetiteAvatar
Flutter
AppetiteAvatar
Compose
AppetiteAvatar
React Native
Badge
SwiftUI
AppetiteBadge
Flutter
AppetiteBadge
Compose
AppetiteBadge
React Native
Button
SwiftUI
AppetiteButton
Flutter
Button
Compose
AppetiteButton
React Native
Checkbox
SwiftUI
AppetiteCheckbox
Flutter
AppetiteCheckbox
Compose
AppetiteCheckbox
React Native
Chip
SwiftUI
AppetiteChip
Flutter
AppetiteChip
Compose
AppetiteChip
React Native
TextInput
SwiftUI
AppetiteTextField
Flutter
TextInput
Compose
AppetiteTextInput
React Native
List
SwiftUI
AppetiteList
Flutter
AppetiteList
Compose
AppetiteList
React Native
ModalSheet
SwiftUI
AppetiteModalSheet
Flutter
ModalSheet
Compose
AppetiteModalSheet
React Native
Navigation
SwiftUI
AppetiteNavigation
Flutter
Navigation
Compose
AppetiteNavigation
React Native
Radio
SwiftUI
AppetiteRadio
Flutter
AppetiteRadio
Compose
AppetiteRadio
React Native
SegmentedControl
SwiftUI
AppetiteSegmentedControl
Flutter
SegmentedControl
Compose
AppetiteSegmentedControl
React Native
Tabs
SwiftUI
AppetiteTabs
Flutter
Tabs
Compose
AppetiteTabs
React Native
Toast
SwiftUI
AppetiteToast
Flutter
Toast
Compose
AppetiteToast
React Native
Toggle
SwiftUI
AppetiteToggle
Flutter
Toggle
Compose
AppetiteToggle
React Native
TopBar
SwiftUI
AppetiteTopBar
Flutter
TopBar
Compose
AppetiteTopBar

React Native: Segmented Control

import { Avatar } from '@appetite-ui/react-native';

<Avatar source={{ uri: user.photoUrl }} accessibilityLabel={user.name} size="2xl" bottomStatus="online" />
<Avatar type="acronym" name="Maren Lindqvist" tone="blue" size="xl" />
<Avatar type="symbol" accessibilityLabel="Unknown person" size="lg" />
<Avatar source={photo} accessibilityLabel="Amara Diallo" topStatus="verified" />
import { Badge } from '@appetite-ui/react-native';

<Badge label="Paid" state="success" variant="color" />
<Badge label="Beta" state="feature" dot />
<Badge type="circle" label="3" state="error" variant="color" />
<Badge type="dot" state="information" accessibilityLabel="Unread" />
import { Button } from '@appetite-ui/react-native';

<Button text="Confirm transfer" size="lg" iconRight="arrow-right" onPress={confirm} />
<Button text="Cancel" type="neutral" variant="link" size="lg" onPress={cancel} />
<Button iconOnly icon="plus" accessibilityLabel="Add item" variant="weak" size="md" onPress={add} />
<Button text="Pay $1,200.00" size="md" loading={paying} style={{ alignSelf: 'stretch' }} />
import { Checkbox } from '@appetite-ui/react-native';

const [agreed, setAgreed] = useState(false);
<Checkbox checked={agreed} onCheckedChange={setAgreed} accessibilityLabel="I agree to the terms" />
import { Chip } from '@appetite-ui/react-native';

// A filter: press to toggle Selected.
<Chip text="Design" selected={picked} onPress={() => setPicked(!picked)} />

// A removable recipient: an avatar up front and a close button (Figma Close Button = true).
<Chip
  type="avatar"
  text="Erik Holmberg"
  avatar={{ type: 'photo', source: erikPhoto }}
  onClose={() => remove('erik')}
/>

<Chip type="emoji" text="Celebrate" emoji="🎉" onPress={pick} />
<Chip type="flag" text="United States" flag={<Image source={usFlag} style={{ width: 20, height: 20 }} />} onPress={pick} />
<Chip type="brand" text="Slack" logo={<Image source={slackLogo} style={{ width: 20, height: 20 }} />} onPress={pick} />
<Chip text="Filters" icon="funnel" onPress={openFilters} />
<Chip text="Archived" disabled />
import { TextInput } from '@appetite-ui/react-native';

const [email, setEmail] = useState('');
<TextInput label="Email" placeholder="you@example.com" value={email} onChangeText={setEmail}
  keyboardType="email-address" autoCapitalize="none" help="We never share it" />

<TextInput type="password" label="Password" iconLeft="lock" value={pw} onChangeText={setPw} />
<TextInput type="prefix" prefix="https://" iconLeft="globe" value={site} onChangeText={setSite} />
<TextInput type="suffix" suffix="kg" iconLeft="weight" value={weight} onChangeText={setWeight} keyboardType="decimal-pad" />
<TextInput type="leftSelection" selection={{ text: '+358', flag: <Flag />, onPress: pickCountry }}
  iconLeft="phone" value={phone} onChangeText={setPhone} error={!valid} help="Check the number" />
import { List } from '@appetite-ui/react-native';

<List label="Account" description="Name, email, password" leadingIcon="user" onPress={openAccount} />
<List size="compact" label="Language" leadingIcon="globe" trailing="text" trailingText="English" onPress={pickLanguage} />
<List size="compact" label="Dark mode" leadingIcon="moon" trailing="toggle" checked={dark} onCheckedChange={setDark} />
<List label="Monthly" description="$9 a month" trailing="radio" checked={plan === 'monthly'} onCheckedChange={() => setPlan('monthly')} />
<List size="compact" label="Squats" leadingIcon="dumbbell" trailing="checkbox" checked={picked.squats} onCheckedChange={(on) => pick('squats', on)} />
import { ModalSheet, List } from '@appetite-ui/react-native';

<ModalSheet
  open={open}
  onClose={() => setOpen(false)}
  title="Notifications"
  subheadline="Choose what we send you"
  primaryAction={{ text: 'Save', onPress: save }}
  secondaryAction={{ text: 'Cancel', onPress: () => setOpen(false) }}
>
  <List size="compact" label="Order updates" trailing="toggle" checked={orders} onCheckedChange={setOrders} />
</ModalSheet>
import { Navigation } from '@appetite-ui/react-native';

const [tab, setTab] = useState('home');
<Navigation
  value={tab}
  onValueChange={setTab}
  items={[
    { key: 'home', label: 'Home', icon: 'house' },
    { key: 'search', label: 'Search', icon: 'search' },
    { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: true },
    { key: 'profile', label: 'Profile', icon: 'user' },
  ]}
  style={{ position: 'absolute', left: 16, right: 16, bottom: insets.bottom }}
/>
import { Radio } from '@appetite-ui/react-native';

const [plan, setPlan] = useState('monthly');
<Radio checked={plan === 'monthly'} onCheckedChange={() => setPlan('monthly')} accessibilityLabel="Monthly" />
<Radio checked={plan === 'yearly'} onCheckedChange={() => setPlan('yearly')} accessibilityLabel="Yearly" />
import { SegmentedControl } from '@appetite-ui/react-native';

const [range, setRange] = useState('Week');
<SegmentedControl options={['Day', 'Week', 'Month']} value={range} onValueChange={setRange} />
<SegmentedControl size="small" options={['Mon', 'Tue', 'Wed', 'Thu', 'Fri']} value={day} onValueChange={setDay} accessibilityLabel="Day of the week" />
import { Tabs } from '@appetite-ui/react-native';

const [section, setSection] = useState('overview');
<Tabs
  value={section}
  onValueChange={setSection}
  items={[
    { key: 'overview', label: 'Overview' },
    { key: 'details', label: 'Details' },
    { key: 'reviews', label: 'Reviews' },
  ]}
  accessibilityLabel="Product sections"
/>
<Tabs size="small" value={tab} onValueChange={setTab} items={[{ key: 'home', label: 'Home', icon: 'house' }, { key: 'saved', label: 'Saved', icon: 'bookmark' }]} />
import { Toast } from '@appetite-ui/react-native';

// Type Long: a title and a description, a link and a dismiss button.
<Toast
  state="success"
  title="Note archived"
  description="It moves to Archive for 30 days."
  link={{ text: 'Undo', onPress: undoArchive }}
  onDismiss={hideToast}
/>

// Type Line: one line, no description.
<Toast type="line" state="warning" title="Offline, changes sync later" link={{ text: 'Retry', onPress: retry }} onDismiss={hideToast} />

// Booleans off: leave out icon, link or onDismiss.
<Toast state="info" title="Copied to clipboard" icon={false} />
import { Toggle } from '@appetite-ui/react-native';

const [dark, setDark] = useState(false);
<Toggle checked={dark} onCheckedChange={setDark} accessibilityLabel="Dark mode" />
import { TopBar } from '@appetite-ui/react-native';

// A detail screen: a back button and a title (Size sm).
<TopBar size="sm" title="Order AP 4192" onBack={() => navigation.goBack()} />

// A tab root: no back button, secondary actions on the right (Size md).
<TopBar
  size="md"
  title="Portfolio"
  actions={{
    type: 'secondaryActions',
    items: [
      { icon: 'bookmark', accessibilityLabel: 'Saved', onPress: openSaved },
      { icon: 'search', accessibilityLabel: 'Search', onPress: openSearch },
    ],
  }}
/>

// A large title with a caption and a main button (Size lg, Actions Main Button).
<TopBar
  title="Account setup"
  caption="Update your account"
  onBack={goBack}
  actions={{ type: 'mainButton', accessibilityLabel: 'Done', onPress: save }}
/>

SwiftUI: Segmented Control

import AppetiteCore

AppetiteAvatar(.acronym(name: "Maren Lindqvist"), size: .xl, bottomStatus: .online)
AppetiteAvatar(.photo(Image("amara")), size: .xl2, accessibilityLabel: "Amara Diallo", topStatus: .verified)
import AppetiteCore

AppetiteBadge(label: "Paid", state: .success, variant: .color)
AppetiteBadge(label: "Beta", state: .feature, dot: true)
AppetiteBadge(circle: "3", state: .error, variant: .color)
import AppetiteCore

AppetiteButton(text: "Pay", type: .brand, variant: .filled, size: .md, iconRight: .glyph(.arrowRight)) { pay() }
AppetiteButton(icon: .glyph(.plus), accessibilityLabel: "Add", size: .lg) { add() }
AppetiteButton(text: "Saving", loading: true)        // ignores presses, VoiceOver says "Loading"
import AppetiteCore

AppetiteCheckbox(checked: AppetiteCheckboxValue(on), accessibilityLabel: "Accept") { on = $0 }
import AppetiteCore

AppetiteChip(text: "Design", selected: picked, onPress: { picked.toggle() })
AppetiteChip(text: "Filters", leading: .icon(.glyph(.circle)), onPress: openFilters)
AppetiteChip(text: "Celebrate", leading: .emoji("🎉"), onPress: pick)
import AppetiteCore

AppetiteTextField(type: .prefix("https://"), label: "Website", optional: true, placeholder: "example.com", value: $site, help: "Without the scheme")
import AppetiteCore

AppetiteList(label: "Language", leadingIcon: .glyph(.globe), trailing: .text("English")) { openLanguages() }
AppetiteList(label: "Dark mode", size: .compact, trailing: .toggle(checked: dark) { dark = $0 })
import AppetiteCore

AppetiteModalSheet(
    title: "Notifications", subheadline: "Choose what we send you", open: open, onClose: { open = false },
    primaryAction: .init(text: "Save", onPress: save), secondaryAction: .init(text: "Cancel") { open = false }
) { AppetiteList(label: "Order updates", size: .compact) }
import AppetiteCore

AppetiteNavigation(items: tabs, value: tab) { tab = $0 }       // two to five AppetiteNavigationItem
import AppetiteCore

AppetiteRadio(checked: on, accessibilityLabel: "Option") { on = $0 }
import AppetiteCore

AppetiteSegmentedControl(options: ["Day", "Week", "Month"], value: range, accessibilityLabel: "Range") { range = $0 }
AppetiteSegmentedControl(options: ["List", "Grid"], value: layout, size: .small) { layout = $0 }
import AppetiteCore

AppetiteTabs(items: [.init(key: "all", label: "All"), .init(key: "open", label: "Open")], value: tab) { tab = $0 }
AppetiteTabs(items: items, value: tab, size: .small, accessibilityLabel: "Order status") { tab = $0 }
import AppetiteCore

AppetiteToast(
    title: "Note archived", description: "It moves to Archive for 30 days.", state: .success,
    link: AppetiteToastLink(text: "Undo", onPress: undo), onDismiss: { shown = false }
)
AppetiteToast(title: "Offline, changes sync later", type: .line, state: .warning)
import AppetiteCore

AppetiteToggle(checked: on, accessibilityLabel: "Notifications") { on = $0 }
import AppetiteCore

AppetiteTopBar(title: "Inbox", caption: "3 unread", actions: .secondaryActions([AppetiteTopBarAction(icon: .glyph(.search), accessibilityLabel: "Search") { search() }]))

Flutter: Segmented Control

import 'package:appetite_ui/appetite.dart';

const AppetiteAvatar.acronym(name: 'Maren Lindqvist', size: AvatarSize.xl, bottomStatus: AvatarBottomStatus.online);
AppetiteAvatar.photo(image: const AssetImage('assets/amara.png'), semanticLabel: 'Amara Diallo', topStatus: AvatarTopStatus.verified);
import 'package:appetite_ui/appetite.dart';

const AppetiteBadge(label: 'Paid', state: BadgeState.success, variant: BadgeVariant.color);
const AppetiteBadge.circle(label: '3', state: BadgeState.error, variant: BadgeVariant.color);
const AppetiteBadge.dot(semanticLabel: 'Unread', state: BadgeState.information);
import 'package:appetite_ui/appetite.dart';

Button(text: 'Save', size: ButtonSize.md, iconLeft: AppetiteIconSlot.name('plus'), onPress: save);
Button.iconOnly(icon: AppetiteIconSlot.name('x'), accessibilityLabel: 'Close', onPress: close);
import 'package:appetite_ui/appetite.dart';

AppetiteCheckbox(checked: on, onCheckedChange: (bool? v) => setState(() => on = v ?? false), accessibilityLabel: 'Accept')
import 'package:appetite_ui/appetite.dart';

AppetiteChip(text: 'Design', selected: picked, onPress: () => setState(() => picked = !picked));
AppetiteChip(text: 'Filters', leading: ChipLeading.icon(AppetiteIconSlot.name('plus')), onPress: openFilters);
AppetiteChip(text: 'Ana', leading: ChipLeading.avatar(avatarImage), onPress: open, onClose: () => remove('Ana'));
import 'package:appetite_ui/appetite.dart';

TextInput(value: text, onChangeText: (String v) => setState(() => text = v), label: 'Email', placeholder: 'you@example.com', help: 'We never share it')
TextInput(value: text, onChangeText: (String v) => setState(() => text = v), label: 'Error', error: true)
import 'package:appetite_ui/appetite.dart';

AppetiteList.text(label: 'Language', trailingText: 'English', leadingIcon: const AppetiteIconSlot.name('globe'), onPress: () {})
AppetiteList.toggle(label: 'Dark mode', size: ListSize.compact, checked: on, onCheckedChange: (bool v) => setState(() => on = v))
import 'package:appetite_ui/appetite.dart';

ModalSheet(
  title: 'Notifications',
  open: open,
  onClose: () => setState(() => open = false),
  primaryAction: ModalSheetAction(text: 'Save', onPress: save),
  secondaryAction: ModalSheetAction(text: 'Cancel', onPress: () => setState(() => open = false)),
  child: content,
)
import 'package:appetite_ui/appetite.dart';

Navigation(
  items: const <NavigationItem>[
    NavigationItem(key: 'a', label: 'Home', icon: AppetiteIconSlot.name('circle')),
    NavigationItem(key: 'b', label: 'Search', icon: AppetiteIconSlot.name('circle'), badge: true),
    NavigationItem(key: 'c', label: 'Me', icon: AppetiteIconSlot.name('circle')),
  ],
  value: tab,
  onValueChange: (String v) => setState(() => tab = v),
)
import 'package:appetite_ui/appetite.dart';

AppetiteRadio(checked: on, onCheckedChange: (bool v) => setState(() => on = v), accessibilityLabel: 'Option')
import 'package:appetite_ui/appetite.dart';

SegmentedControl(options: const ['Day', 'Week', 'Month'], value: range, onValueChange: (v) => setState(() => range = v), semanticLabel: 'Range')
SegmentedControl(options: const ['List', 'Grid'], value: layout, onValueChange: onLayout, size: SegmentedControlSize.small)
import 'package:appetite_ui/appetite.dart';

Tabs(items: const <TabsItem>[TabsItem(key: 'all', label: 'All'), TabsItem(key: 'open', label: 'Open')], value: tab, onValueChange: setTab);
import 'package:appetite_ui/appetite.dart';

Toast(
  title: 'Note archived',
  description: 'It moves to Archive for 30 days.',
  state: ToastState.success,
  link: ToastLink(text: 'Undo', onPress: undo),
  onDismiss: () => setState(() => shown = false),
);
const Toast(title: 'Offline, changes sync later', type: ToastType.line, state: ToastState.warning);
import 'package:appetite_ui/appetite.dart';

Toggle(checked: on, onCheckedChange: (bool v) { on = v; }, accessibilityLabel: 'Notifications')
import 'package:appetite_ui/appetite.dart';

TopBar(
  title: 'Inbox',
  caption: '3 unread',
  actions: TopBarSecondaryActions(items: <TopBarAction>[TopBarAction(icon: const AppetiteIconSlot.name('plus'), accessibilityLabel: 'Add', onPress: () {})]),
  onBack: () {},
)

Compose: Segmented Control

import com.appetiteui.components.*

AppetiteAvatar(AvatarContent.Acronym("Maren Lindqvist"), size = AvatarSize.Xl, bottomStatus = AvatarBottomStatus.Online)
AppetiteAvatar(AvatarContent.Photo(imageBitmap), size = AvatarSize.Xl2, contentDescription = "Amara Diallo", topStatus = AvatarTopStatus.Verified)
import com.appetiteui.components.*

AppetiteBadge("Paid", state = BadgeState.Success, variant = BadgeVariant.Color)
AppetiteBadge("3", type = BadgeType.Circle, state = BadgeState.Error, variant = BadgeVariant.Color)
AppetiteBadge(type = BadgeType.Dot, state = BadgeState.Information, contentDescription = "Unread")
import com.appetiteui.components.*
import com.appetiteui.core.*

AppetiteButton(text = "Save", size = ButtonSize.Md, iconLeft = AppetiteIconSlot.Named("plus"), onPress = ::save)
AppetiteButton(iconOnly = true, icon = AppetiteIconSlot.Named("x"), accessibilityLabel = "Close", onPress = ::close)
import com.appetiteui.components.*

AppetiteCheckbox(checked = if (on) CheckboxActive.True else CheckboxActive.False, onCheckedChange = { on = it }, accessibilityLabel = "Accept")
import com.appetiteui.components.*
import com.appetiteui.core.*

AppetiteChip(text = "Design", selected = picked, onPress = { picked = !picked })
AppetiteChip(text = "Filters", leading = ChipLeading.Icon(AppetiteIconSlot.Named("plus")), onPress = ::openFilters)
AppetiteChip(text = "Ana", leading = ChipLeading.Avatar { AvatarImage() }, onPress = ::open, onClose = { remove("Ana") })
import com.appetiteui.components.*

AppetiteTextInput(value = text, onChangeText = { text = it }, label = "Email", placeholder = "you@example.com", help = "We never share it")
AppetiteTextInput(value = text, onChangeText = { text = it }, label = "Error", error = true)
import com.appetiteui.components.*
import com.appetiteui.core.*

AppetiteList(label = "Language", leadingIcon = AppetiteIconSlot.Named("globe"), trailing = ListTrailing.Text("English", onPress = {}))
AppetiteList(label = "Dark mode", size = ListSize.Compact, trailing = ListTrailing.Toggle(on) { on = it })
import com.appetiteui.components.*

AppetiteModalSheet(
    title = "Notifications",
    open = open,
    onClose = { open = false },
    subheadline = "Choose what we send you",
    primaryAction = ModalSheetAction("Save", ::save),
    secondaryAction = ModalSheetAction("Cancel", { open = false }),
) { AppetiteList(label = "Order updates") }
import com.appetiteui.components.*
import com.appetiteui.core.*

AppetiteNavigation(
    items = listOf(
        NavigationItem("a", "Home", AppetiteIconSlot.Named("circle")),
        NavigationItem("b", "Search", AppetiteIconSlot.Named("circle"), badge = true),
        NavigationItem("c", "Me", AppetiteIconSlot.Named("circle")),
    ),
    value = tab, onValueChange = { tab = it },
)
import com.appetiteui.components.*

AppetiteRadio(checked = on, onCheckedChange = { on = it }, accessibilityLabel = "Option")
import com.appetiteui.components.*

AppetiteSegmentedControl(listOf("Day", "Week", "Month"), range, { range = it }, contentDescription = "Range")
AppetiteSegmentedControl(listOf("List", "Grid"), layout, { layout = it }, size = SegmentedControlSize.Small)
import com.appetiteui.components.*

AppetiteTabs(listOf(TabsItem("all", "All"), TabsItem("open", "Open")), value = tab, onValueChange = { tab = it })
AppetiteTabs(items, value = tab, onValueChange = { tab = it }, size = TabsSize.Small)
import com.appetiteui.components.*

AppetiteToast(
    "Note archived",
    description = "It moves to Archive for 30 days.",
    state = ToastState.Success,
    link = ToastLink("Undo", ::undo),
    onDismiss = { shown = false },
)
AppetiteToast("Offline, changes sync later", type = ToastType.Line, state = ToastState.Warning)
import com.appetiteui.components.*

AppetiteToggle(checked = on, onCheckedChange = { on = it }, accessibilityLabel = "Notifications")
import com.appetiteui.components.*
import com.appetiteui.core.*

AppetiteTopBar(
    title = "Inbox", caption = "3 unread",
    actions = TopBarActions.SecondaryActions(listOf(TopBarAction(AppetiteIconSlot.Named("plus"), "Add") {})),
    onBack = {},
)

Limits

What Code 1.0 does not do.

System chrome.

The status bar, keyboard, Dynamic Island, widgets and Live Activities are drawn by the OS.

Apple and Material 3 replicas.

Those reference components stay in the Figma file.

Every component in SwiftUI, Flutter and Compose. In 1.x

They start with the core set, and the rest arrives in free updates.

Pricing

One payment. Yours forever.

Licence for

Figma

$129$97$78$59

$299$225$180$135

USD, paid once

For designing iOS and Android apps.

  • Components and app screens in light and dark mode
  • Apple and Material 3 in one file
  • 589 variables in 6 collections
  • Tokens for SwiftUI, Jetpack Compose, Flutter and CSS
  • Core Skill for AI agents

Code

$79

$179

USD, paid once

For building iOS and Android apps in code.

  • Every Appetite UI component in React Native
  • The core components in SwiftUI, Flutter and Jetpack Compose, more in every release
  • Same names and variants as the Figma file
  • Tokens for every stack and CSS
  • iOS and Material conventions behind one switch
  • Rebrand in one theme file
  • App Starter with Tasks, Foodie and AI flows
  • Agent kit for Claude Code and Cursor

Code starts at $79. The price rises with every release, and every release is included.

Complete

$179

$399

USD, paid once

Figma + Code. $29 less than both, on Team $79 less.

  • Everything in Figma
  • Everything in Code
  • Designers and developers on the same components

FAQ

Questions worth asking.

Still have a question?

Write to us. It lands in one inbox, read by the people who build Appetite UI.

No. Code works on its own and ships with its own tokens. Get Complete if you or your team design in Figma.

No. The components are source code you copy into your project and own. Only the theme core installs as a package for your stack, so an update replaces that package and leaves your code alone.

Yes. Code ships with an agent kit, and the licence allows Claude Code, Cursor and other coding agents. It does not allow training models on the files or building tools that generate UI for others.

React Native with Expo, SwiftUI, Flutter and Jetpack Compose, for iOS and Android. React Native has every Appetite UI component and the App Starter. SwiftUI, Flutter and Compose start with 15 core components, and every release adds more. Apple and Material 3 reference components stay in the Figma file.

Yes. Every Code update is included, new components and stacks too.

Yes. The price of Code rises with every release. Every release after you buy is included.

React Native StyleSheet with a typed theme. No NativeWind, no Tailwind. The App Starter runs on Expo SDK 58 with Expo Router. It also works in a React Native app without Expo once Expo modules are installed (npx install-expo-modules).

Yes. The React Native folder is built and tested on Expo SDK 58. The App Starter uses Expo Router and opens in Expo Go.
brand.ts

Your next rebrand is one edit.

Change the brand color, radius and font in one theme file, and every component in your stack follows.