# Appetite UI documentation Appetite UI is a Figma UI kit for iOS and Android apps, built like a design system. One token source drives Apple and Material components in light and dark mode, and exports to SwiftUI, Jetpack Compose, Flutter and CSS. Every page of https://www.appetiteui.com/docs/introduction in one file, in rail order, after a short summary of the product. Generated at build from content/docs and lib/site.config.ts. The token values the docs previews draw are at https://www.appetiteui.com/tokens.json, as W3C DTCG. ## About the product - Version: 2.4, the release on sale. - Platforms: iOS and Android. Apple and Material conventions live in one Figma file, in light and dark mode. - Tokens: 544 variables in 6 collections, one W3C DTCG source that exports to SwiftUI, Jetpack Compose, Flutter and CSS. - Price: One payment, lifetime updates: Solo $79 for one designer, Team $149 for up to ten people in one organization, Enterprise on request (hi@appetiteui.com). No refunds: a read-only preview of the whole file comes first. - Terms: https://www.appetiteui.com/terms-and-conditions. License: https://www.appetiteui.com/license. - Read-only preview of the full file: https://www.figma.com/design/iNFK9Pl2e9vEeClqhm1F4t/Appetite-UI-%E2%9C%A6-2.4?node-id=10-602 - Contact: hi@appetiteui.com or https://www.appetiteui.com/contact, answered by the designer who builds the kit. ## Plans The same Figma file on every plan. Solo, Team and Enterprise open one file. The license sets how many people work in it. - App screens to start from: Finance, booking, fitness, social, AI and food flows, built from the same components. - Apple and Material 3 in one file: 21 Apple and 17 Material 3 pattern pages on the same tokens. - 544 variables in 6 collections: Light and dark color, type, spacing, radius and motion. - Token source and outputs: One W3C DTCG source, exported to SwiftUI, Jetpack Compose, Flutter and CSS. ### Solo, $79 (USD, paid once) One designer, unlimited projects. - Personal and client work, no project limit - Edit components, variables and styles for your brand - Share design files with clients and developers - Every future release ### Team, $149 (USD, paid once) Up to ten people on one shared system. - Everything in Solo - Ten people sharing the same source of truth - Shared library setup guidance - Direct support from the maker ### Enterprise (Quoted in writing) Several product teams on one private library. - Everything in Team - Unlimited users - A derived private library for your brand - Every future release Buy Solo or Team: https://appetiteui.lemonsqueezy.com/checkout/buy/f6a62afc-a2d5-43e2-aae5-1ca0e3417077. Enterprise: hi@appetiteui.com. Pricing page: https://www.appetiteui.com/pricing. --- --- title: Introduction description: Appetite UI is a Figma UI kit for iOS and Android apps, built like a design system: what it is, who it is for and why it is mobile only. group: Get started source: https://www.appetiteui.com/docs/introduction --- # Introduction Appetite UI is a Figma UI kit for iOS and Android apps, built like a design system. One token source drives Apple and Material components in light and dark mode, and exports to SwiftUI, Jetpack Compose, Flutter and CSS. Apple’s and Google’s kits give you each platform’s baseline; this is the layer between them and your brand, already built. ## What it is Built only for iOS and Android apps. Production-ready components, a structured token system and platform-aware native elements, all running on Figma’s own variables. No plugins. No workarounds. ![Mobile app screens drawn with Appetite UI](/images/docs-intro-screens.webp) ## Where it fits Apple’s and Google’s conventions stay the baseline, and your brand sits on top of them. Appetite UI does not replace the platform kits. It names their slots and points them at your tokens: 112 bridge variables, 60 Material 3 slots and 52 Apple slots, in two modes. Switch to System mode and the same slot points back at the platform value, so you can check a screen against the native palette without unpicking anything. ## A system, not a collection A component never names a color. It names a token, the token names a foundation, and the chain runs one way only. | Collection | What it decides | Variables | | --- | --- | --- | | Foundations | Raw color ramps, and the spacing, radius, border and icon scales | 196 | | Color Tokens | Semantic color in Light and Dark: bg, text, border, fg, state, brand | 161 | | Platform Bridge | Material 3 and Apple slot names, in Brand and System modes | 112 | | Motion | 5 durations, 5 easing curves, 3 springs, 7 presets built from them | 31 | | Typography | Size, line height, tracking, weight and family | 28 | | Platform | Touch targets, safe areas, bar heights and corners, per platform | 16 | Six collections, 544 variables, one direction of travel. Nothing points back up the chain, which is what lets the structure survive scale.{' '} `platform/min-touch-target` reads 44 in both iOS modes and 48 in Android, so one component obeys both without a second copy. ## It reaches the code One W3C Design Tokens source. It generates CSS, SwiftUI, Compose and Flutter, so the value your engineer ships is the value you drew. Some values need no translation at all: `motion/easing/standard` already holds{' '} `cubic-bezier(0.2, 0, 0, 1)` and `motion/duration/base` holds 200. This is the claim the whole system is built to keep, and [Code output](/docs/code-output) shows the exporter and the generated files. ## What is inside A complete mobile toolkit, scoped to what actually matters. All of it in one Figma file, all of it reading from one set of variables. | Area | What you get | Count | | --- | --- | --- | | Components | Built for touch, every state bound to a token | 36 pages | | Apple patterns | iOS conventions redrawn in Appetite UI geometry | 21 pages | | Material 3 patterns | Material conventions redrawn in Appetite UI geometry | 17 pages | | Application screens | Onboarding, Settings, Empty State, Finance, Fitness, Social, AI, Booking, Foodie | 9 sets | | Icons | Lucide, every one running off a single stroke weight variable | 1,667 | | Styles | 20 text, 19 effect, 3 grid, and deliberately no paint styles | 42 | Inter is licensed under the SIL Open Font License 1.1 and Lucide under the ISC License, both cleared for commercial work. The Apple and Material pattern pages carry no vendor design resource. The Assets page ships brand marks and emoji so you can populate a realistic mock-up: they belong to their owners, and you replace them before you ship. --- --- title: Install the library description: Open the file, publish it as a library, enable it in your project, and check that variables and components arrive. Five minutes, once. group: Get started source: https://www.appetiteui.com/docs/install --- # Install the library Learn how to set up a new project with Appetite UI. Whether you duplicate the file or use it as a published library, this page covers the workflow that gets you from zero to designing in minutes. ## Before you start Figma features you should know Appetite UI is built on Figma’s Auto Layout, components, variants, interactive components, and Variables. Understanding these fundamentals helps you get the most out of the system. If you’re comfortable with Figma, you’re ready to go. If you’re still getting familiar with it, these two resources will get you up to speed fast: ## How to use this file Two approaches, depending on how you work: Option 1: Duplicate per project. Right-click the Appetite UI file and hit Duplicate. You get a standalone copy. Strip out the components you don’t need, customize the tokens, and start designing. Best for solo designers or one-off projects where you want full control. ![Figma file menu with Duplicate selected](/images/docs-install-duplicate.png) Right-click the Appetite UI file and hit Duplicate. Option 2: Use it as a shared library. Keep Appetite UI as a single source file and publish it as a Figma library. Every project pulls components from the same place, and updates flow automatically. Best for teams or anyone running multiple projects off the same system. The right choice depends on your setup. If you’re working alone on a single app, duplicating is faster. If you’re managing consistency across projects or collaborating with other designers, the library approach pays off quickly. For option 2, consider splitting Appetite UI into smaller, focused libraries: one for components, one for tokens, one for icons. It takes upfront effort, but it gives your team granular control over what gets published and updated. ## Library Publish and Use as a Library Publishing turns your Appetite UI file into a shared library that any Figma project can pull from. Components, styles, and variables all become available through the Assets panel in every file that enables the library. The key benefit: when you update a component or token in the source file and re-publish, every file using that library gets a notification. Your team decides when to accept updates. Nothing changes without explicit action. The library becomes your single source of truth across all design work. ## Maintenance Keep your file clean Appetite UI ships with a lot of components. You won’t need all of them for every project. Go through the file at the start of each project and delete the pages and components you know you won’t use. Right-click any page in the left sidebar and hit “Delete page.” ![Figma pages list with Delete page selected](/images/docs-install-delete-page.png) Right-click a page in the left sidebar and hit Delete page. This reduces file size, speeds up publishing, and cuts down the noise when browsing the Assets panel. Don’t worry about being too aggressive. You can always re-duplicate from the original if you need something back. A lean file is a fast file. --- --- title: Colors description: The Appetite UI color system: primitives, semantic roles, light and dark modes, and the alias chain that carries a brand change through every component. group: Foundations source: https://www.appetiteui.com/docs/colors --- # Colors Ten primitive ramps feed 159 semantic roles across light and dark. Bind a component to the role and the value follows the mode, the platform and the code. ## Overview Foundations are the raw color values that power the entire system. Think of them as your source of truth: every token, every component color, and every light and dark mode mapping traces back to a foundation variable. Each hue follows a consistent 50 to 950 scale, giving you 11 shades from near-white to near-black. Gray adds a 0 step and a 150 step, so it carries 13. The naming is predictable: blue-500 is always the midpoint, blue-50 is the lightest tint, blue-950 is the deepest shade. Every hue uses the same logic, so once you learn one, you know them all. The 150 step is a hairline surface, and the overview board in the Figma file does not draw it, so the board shows 12 gray tiles where this page shows 13. The variables are the source and they carry 13. Read from the file on 8 September 2026. Every hex, step and alias on this page came from that read. ## Gray Figure: the gray ramp, every step with its hex in light and dark. ## Blue Figure: the blue ramp, every step with its hex in light and dark. ## Red Figure: the red ramp, every step with its hex in light and dark. ## Orange Figure: the orange ramp, every step with its hex in light and dark. ## Yellow Figure: the yellow ramp, every step with its hex in light and dark. ## Green Figure: the green ramp, every step with its hex in light and dark. ## Purple Figure: the purple ramp, every step with its hex in light and dark. ## Sky Figure: the sky ramp, every step with its hex in light and dark. ## Pink Figure: the pink ramp, every step with its hex in light and dark. ## Teal Figure: the teal ramp, every step with its hex in light and dark. ## Alpha Colors Figure: the alpha sets, each swatch over a split white and black ground. Each tile is painted over a split ground, white on the left and static-black-950 on the right, so a translucent value reads translucent against both. Beside the nine hues the file carries four neutral alpha sets: black and gray at 10, 16 and 24 percent, white also at 72, 88 and 92, gray-900 at 55, 72 and 88. They back the glass layers and the tinted status fills. ## Semantic roles A role says what a colour is for, so the decision gets made once and every component inherits it. Reach for a role by its job, never by its hue. Each swatch is cut on the diagonal: Light above the cut, Dark below it. Where a role holds the same value in both modes the cut disappears, and that is the correct reading. Figure: the semantic roles, grouped as the Figma collection groups them. Grouped the way the Figma collection is grouped. Each status family also carries a light-200, a lighter-50 and a dark-950 step; the tinted pair is drawn under Modes. The iOS and Material 3 mirrors belong to the Platform Bridge page, not here. ## Modes Color Tokens carries two modes, Light and Dark. Foundations carries one. A role does not change its value in Dark, it changes which primitive it points at. text/strong-950 points at gray/950 in Light and at gray/0 in Dark, so the token inverts on its own and no component needs a dark variant. One asymmetry is worth knowing. In Light the light-200 and lighter-50 steps of a status family alias solid ramp steps, 200 and 50. In Dark they alias alpha-24 and alpha-16 of the same hue. A tinted surface therefore stays translucent on a dark ground instead of turning into a solid pastel block. That is deliberate. It is why a soft error banner reads as a tint in Dark and not as a slab. Figure: the same roles resolved in light and in dark. Four of the ten status families. The same pattern runs through away, feature, highlighted, stable, verified and faded. ## Tokens The Appetite UI semantic set, resolved. The small line under each hex names what the value lands on: a Foundations step, an alpha of one, direct where the role holds a raw hex and a ramp edit will not reach it, or fixed value where the role must never switch. Copy the token name, never the hex. Figure: one role followed from its semantic name to its primitive. A value carrying a percentage is an alpha role, drawn over the ground it sits on: white in Light, near black in Dark. --- --- title: Typography description: The Appetite UI type scale in Inter: sizes, line heights, weights and letter spacing, and how the ramp maps to iOS Dynamic Type and Material 3 type roles. group: Foundations source: https://www.appetiteui.com/docs/typography --- # Typography Ten sizes, two weights, one typeface. Every text style reads its size, line height and tracking from variables, so the whole ramp moves together. ## Preview One typeface carries the system. Inter, ten sizes, two weights, the same in Figma and in every target the tokens generate. Figure: the type ramp, one line per step. Figma lists the face as Inter Variable and ships Regular and Semi Bold as named styles. In code the family is Inter. One face, two names, one variable behind both. ## Scale Ten steps, and that is the whole vocabulary. Every line below is set in the step it names, at its real size, with its own line height and letter spacing applied. Figure: the type scale, every step with its size and line height. Letter spacing moves in three bands, not per step. Wide +0.1 on xs and sm, normal 0 on base, lg and xl, tight -0.4 on 2xl and up. That is the file’s own rule, and it is why the ramp holds at both ends: small text opens, large text closes. Line height has its own tell. 18 and 20 both set on 28, so lg and xl share one rhythm. ## Weights Two weights. Regular 400 is Default, Semi Bold 600 is Emphasized. Each of the ten steps ships twice, once in each, and the pair shares one size, one line height and one letter spacing. Figure: the weights the family ships. Two weights is a decision, not a gap. No text style and no variable defines a Medium, so there is nothing to bind a layer to and nothing to drift toward. Hierarchy comes from size and spacing. ## Platforms The ramp is wired to both platforms as variables, not as a note in a description. Every Apple text style and every Material 3 type role resolves to a step below. | Step | Apple text style | Material 3 role | | --- | --- | --- | | 6xl 60 | none | displayLarge | | 5xl 48 | none | displayMedium | | 4xl 36 | largeTitle | displaySmall | | 3xl 30 | title1 | headlineLarge, headlineMedium | | 2xl 24 | none | headlineSmall | | xl 20 | title2, title3 | titleLarge | | lg 18 | headline, body | none | | base 16 | callout | titleMedium, bodyLarge | | sm 14 | subheadline | titleSmall, bodyMedium, labelLarge | | xs 12 | footnote, caption1, caption2 | bodySmall, labelMedium, labelSmall | That is Brand mode. The same variables have a System mode that returns the platform’s own published numbers instead, Apple’s 17pt body and Material’s 57sp displayLarge among them, so a mirror screen can switch platform without being rebuilt. Set the mode on the frame and everything inside follows. The bridge carries the size and only the size. Line height, weight and tracking stay on the Appetite UI side, so a step keeps its rhythm whichever platform name you reach it by. Dynamic Type and Material scaling are runtime behaviour no Figma file can model: read every number here as the default size class. ## Tokens Twenty text styles resting on twenty eight variables. The styles are what you apply to a layer; the variables are what move when you change your mind. | Style | Size / line | Tracking | Role | | --- | --- | --- | --- | | 6xl | 60 / 64 | -0.4 | Hero numerals and splash headlines. Rare in mobile. | | 5xl | 48 / 52 | -0.4 | Display. Onboarding hero and large balances. | | 4xl | 36 / 40 | -0.4 | Display small. Screen hero titles. | | 3xl | 30 / 36 | -0.4 | Headline large. Section heroes. | | 2xl | 24 / 32 | -0.4 | Headline. Screen titles and modal headers. | | xl | 20 / 28 | 0 | Title large. Card titles and sheet headers. | | lg | 18 / 28 | 0 | Title. List section headers and emphasized body. | | base | 16 / 24 | 0 | Body. The default reading size. | | sm | 14 / 20 | 0.1 | Body small. Secondary text and list subtitles. | | xs | 12 / 16 | 0.1 | Caption and label. Badges, timestamps, tab labels. | Each row is two styles, not one. 3xl/default is Regular 400 and 3xl/emphasized is Semi Bold 600; size, line height and tracking are identical across the pair. base is the only style carrying paragraph spacing, 16, because it is the only one that regularly runs past a line. | Variable | Value | Reaches | | --- | --- | --- | | fontFamily/Body | Inter | Aliases platform/font/brand | | fontFamily/Heading | Inter | The same alias. One face by design | | fontFamily/System | Inter | Operating system surfaces only. No style reaches it | | fontWeight/default | 400 | The ten /default styles | | fontWeight/emphasized | 600 | The ten /emphasized styles | | letterSpacing/tight | -0.4 | 2xl, 3xl, 4xl, 5xl, 6xl | | letterSpacing/normal | 0 | base, lg, xl | | letterSpacing/wide | 0.1 | sm, xs | Ten sizes and ten line heights complete the collection, one matched pair per step. The family variables are no longer the control point on their own: both resolve through platform/font/brand, so repoint that one value and the whole system moves, Figma and generated code together. Change a family variable alone only if headings should break away from body. --- --- title: Icons description: 1,667 Lucide icons on a single stroke weight variable. Sizes, stroke, alignment and how to swap an icon inside a component without detaching it. group: Foundations source: https://www.appetiteui.com/docs/icons --- # Icons 1,667 Lucide icons on one box and one stroke variable, so an icon at 16 and an icon at 32 still belong to the same set. ## Preview 1,667 icons on one box, one stroke and one pair of end caps. The set is Lucide, a third party open source library under the ISC licence. We did not draw it. We wired it to the tokens. Specimen: 32 of 1,667, drawn at 24. 24 box, stroke 2, round cap, round join. The set is flat, no variant sets, filed under 19 categories on the overview board and named in Lucide's own kebab case: house, chevron-right, message-circle. The layer name is the search term. ## Sizes Four steps for interface work and one for display. The box changes, the drawing inside it does not. Specimen: One icon at every step, on a common baseline. 16, 20, 24, 32, 96. Size is set by the component that holds the icon, not bound on the icon itself. In Button the icon sits at 20 beside a label and at 24 when the button is icon only. The five steps are the ramp those components resize to. ## Stroke One variable holds the stroke at 2 while the box changes. Leave it out and the line thickens with the artwork, so a 32 icon reads heavier than the 16 beside it. Specimen: The same icon at 16, 20, 24, 32. Read the second line under each glyph. Above it holds at 2, below it drifts. In the file this is not arithmetic. The vector scales with its frame while strokeWeight stays bound to `dimensions/icon/stroke`, so the line holds at 2 whatever the box does. 1,666 of the 1,667 carry that binding. The exception is circle-check, the one vector drawn on the full 24 box rather than inset. ## Colour An icon has no colour of its own. It takes the foreground role of the text beside it, so a label and its icon never drift apart. Specimen: One icon, six foreground roles. The stroke is a variable in every case, never a literal. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `fg/strong-950` | #0E0E0F | #FFFFFF | The default in the library | | `fg/sub-700` | #48484A | #C7C7CC | Neutral outline controls | | `fg/disabled-400` | #AEAEB2 | #8E8E93 | Disabled states | | `brand/base` | #155DFC | #2B7FFF | Brand outline controls | | `state/error/base-500` | #FB2C36 | #FB2C36 | Destructive actions | All five were read off icon instances inside Button. On a filled button the icon takes `fg/white-0`, #FFFFFF light and #0E0E0F dark. ## Tokens Six numbers cover the whole set. Five sizes and one stroke, all in the Foundations collection, one mode. | Token | Value | Used for | | --- | --- | --- | | `dimensions/icon/size/sm` | 16 | Dense rows, inline meta | | `dimensions/icon/size/md` | 20 | An icon beside a label | | `dimensions/icon/size/base` | 24 | The default, and the frame itself | | `dimensions/icon/size/lg` | 32 | The largest interface step | | `dimensions/icon/size/display` | 96 | Display only, never inside a control | | `dimensions/icon/stroke` | 2 | strokeWeight on every vector | Sizes are scoped to width and height, the stroke to stroke weight, so each offers itself only where it belongs. Only the stroke is bound on the icon: size comes from the component holding the instance. --- --- title: Dimensions description: Spacing, radius, icon and stroke scales in Appetite UI. The numbers every component reads, and the reason there are no arbitrary values in the system. group: Foundations source: https://www.appetiteui.com/docs/dimensions --- # Dimensions Dimensions holds the four scales every measurement in the system comes from: spacing, radius, icon size and border weight. Reach for a step, never a number. ## Preview The spacing ramp, every step at its real width. Read the rhythm before the numbers. Figure: the scale scale, every step with its value. The ramp starts at spacing/px. spacing/0 is a real step and draws nothing, so twenty two bars stand for twenty three steps. ## Spacing 23 steps, from one pixel to 160. The step number is the value divided by four, so spacing/4 is 16 and spacing/12 is 48. Figure: the scale scale, every step with its value. Half steps stop at spacing/2-5 and the ramp thins above spacing/12: 14, 16, 20, 24, 32, 40. There is no spacing/13, spacing/15 or spacing/18, and that is the point of a scale. ## Radius Nine steps on one identical square, so the corner is the only thing that changes. The scale is named, not numbered: a radius is a shape decision. Figure: the radii scale, every step with its value. radius/full stores 9999, not 50 percent, so a tall box keeps semicircular ends instead of becoming an ellipse. It gets a wider swatch because full on a square is only a circle. radius/xl sits at 18, off the doubling the steps below it follow. ## Icon Five sizes and one stroke. dimensions/icon/stroke is 2 at 16 and 2 at 96, so icons of different sizes in one row still read as one family. Figure: the icons scale, every step with its value. All 1,667 Lucide icons run off dimensions/icon/stroke. Change that one variable and every icon changes weight together. The marks are drawn to Lucide geometry at each size, not exported art. ## Tokens Every value in the collection. 42 variables in one mode, across four scales. None is an alias; each is a raw number the rest of the system points at. | Token | Value | Used by | | --- | --- | --- | | `dimensions/spacing/0` | 0 | No gap | | `dimensions/spacing/px` | 1 | Hairline | | `dimensions/spacing/0-5` | 2 | Icon to label | | `dimensions/spacing/1` | 4 | Tight gap | | `dimensions/spacing/1-5` | 6 | Chip padding | | `dimensions/spacing/2` | 8 | Label to control | | `dimensions/spacing/2-5` | 10 | Compact row | | `dimensions/spacing/3` | 12 | Field padding | | `dimensions/spacing/4` | 16 | Card padding | | `dimensions/spacing/5` | 20 | Row gap | | `dimensions/spacing/6` | 24 | Card to card | | `dimensions/spacing/7` | 28 | Header padding | | `dimensions/spacing/8` | 32 | Block gap | | `dimensions/spacing/9` | 36 | Wide block gap | | `dimensions/spacing/10` | 40 | Section padding | | `dimensions/spacing/11` | 44 | Tap target | | `dimensions/spacing/12` | 48 | Section gap | | `dimensions/spacing/14` | 56 | Wide section gap | | `dimensions/spacing/16` | 64 | Screen block gap | | `dimensions/spacing/20` | 80 | Screen padding | | `dimensions/spacing/24` | 96 | Empty state | | `dimensions/spacing/32` | 128 | Hero spacing | | `dimensions/spacing/40` | 160 | Full bleed | | `dimensions/radius/none` | 0 | Flush edges | | `dimensions/radius/sm` | 2 | Badge, tag | | `dimensions/radius/base` | 4 | Input, small chip | | `dimensions/radius/md` | 8 | Button, field | | `dimensions/radius/lg` | 12 | Card | | `dimensions/radius/xl` | 18 | Sheet, modal | | `dimensions/radius/2xl` | 24 | Large card | | `dimensions/radius/3xl` | 32 | Large surface | | `dimensions/radius/full` | 9999 | Pill, avatar | | `dimensions/icon/size/sm` | 16 | Inline with text | | `dimensions/icon/size/md` | 20 | List row, tab bar | | `dimensions/icon/size/base` | 24 | Default size | | `dimensions/icon/size/lg` | 32 | Feature row | | `dimensions/icon/size/display` | 96 | Empty state art | | `dimensions/icon/stroke` | 2 | Every Lucide icon | | `dimensions/border/0-75` | 0.75 | Sub pixel hairline | | `dimensions/border/1` | 1 | Default border | | `dimensions/border/2` | 2 | Focus ring, selected | | `dimensions/border/4` | 4 | Emphasis rule | Border weight is the fourth scale and gets no section of its own. dimensions/border/0-75 is the only fractional value in Dimensions. The third column is guidance for picking a step, not a usage index read out of the file. --- --- title: Effects description: The elevation ramp, backdrop blur and Liquid Glass styles in Appetite UI. Six shadow steps, what each one is for, and the components bound to them. group: Foundations source: https://www.appetiteui.com/docs/effects --- # Effects Six drop shadows, four backdrop blurs and nine Liquid Glass styles. The shadow ramp is the part that reaches the code, and every surface that lifts is bound to one of its steps. ## Preview The file holds 19 effect styles: six drop shadows, four backdrop blurs and nine Liquid Glass. The six shadows are the ramp, and they are the part that reaches the code. Here they are in order on one surface, so the ladder is a single glance instead of six comparisons. Specimen: dropShadow, xs to 2xl. one surface, six steps, black at 4 to 12 percent. Every step is two layers and both are pure black. Nothing in the ramp carries a hue, and nothing carries a second value for dark mode. The hairline on each tile is docs chrome: at 4 percent, a white surface on a white stage is otherwise indistinguishable from the stage. ## Elevation Six steps, and the file names what each one is for. Read the description before the numbers. The step is chosen by what the surface is, never by how deep the shadow happens to look. Figure: the elevation levels, each with what it is for. Switch this page to dark and the ladder above all but collapses. That is not a fault in the page. A 5 percent black shadow has almost nothing to say against a #0E0E0F surface, and the ramp ships one set of values for both modes, in the file and here. The Card page already answers this with a deliberate dark override at 30 and 14 percent, and until the file gives the ramp a dark value, any component that has to read as lifted on a dark surface needs the same kind of local decision. ## Blur Four backdrop blurs: 8, 16, 24 and 40. A blur needs something behind it, so a radius only means anything against a real ground. These four are documented, not drawn. The shared stylesheet declares the shadow ramp and deliberately declares none of these. A Figma blur radius is not a CSS blur() radius, and the two have never been measured against each other. So the four radii are documented as Figma values, and nothing here converts them. Read them as a sequence, not as a conversion. ## Liquid Glass Nine styles, measured from Apple Design Resources, in two tiers: surface for sheets, alerts, action sheets, menus and keyboards, and control for buttons, the page control, toolbar items and the segmented control. In Figma the glass is a GLASS effect, which bends what sits behind it as well as blurring it. Each tier ships twice: as one style, and as Apple's two-layer build, a fill layer that carries the tint, the lift and the hairline rim, under a glass layer that carries the refraction and the light on the rim. The clear control pair is the lighter variant with no rim glow, and liquidGlass/keyboard is the keyboard's own background. CSS has no refraction, so the web has nothing for these styles to differ by. The table below marks every glass layer as having no equivalent, and that is the honest result. Use liquidGlass in the file, reach for the platform's own glass material in code, and treat the token as a Figma instruction rather than a web spec. ## Tokens All 19 effect styles. Shadow offsets read x y blur spread in px, and every shadow layer is pure black at the stated alpha. | Token | Value | Where it belongs | | --- | --- | --- | | `dropShadow/xs` | 0 1 2 spread 0, 4% 0 4 4 spread 0, 1% | Subtle lift. Chips and small pressables. | | `dropShadow/sm` | 0 9 10 spread -2, 3% 0 3 5 spread -2, 5% | Cards at rest. | | `dropShadow/md` | 0 10 15 spread -3, 5% 0 4 6 spread -4, 8% | Raised cards and dropdowns. | | `dropShadow/lg` | 0 12 18 spread -3, 8% 0 5 6 spread -5, 5% | Popovers and menus. | | `dropShadow/xl` | 0 14 20 spread -5, 8% 0 6 10 spread -5, 10% | Modals and sheets. | | `dropShadow/2xl` | 0 25 30 spread -6, 12% 0 8 10 spread -6, 12% | Full-screen overlays. | | Token | Value | On the web | | --- | --- | --- | | `backdropBlur/sm` | radius 8 | Approximated, blur(8px) | | `backdropBlur/md` | radius 16 | Approximated, blur(16px) | | `backdropBlur/lg` | radius 24 | Approximated, blur(24px) | | `backdropBlur/xl` | radius 40 | Approximated, blur(40px) | | `liquidGlass/surface` | frost 16, refraction 0.7, depth 30, over a 48 lift | No equivalent | | `liquidGlass/control` | frost 8, refraction 0.7, depth 30, over a 6 lift | No equivalent | | `liquidGlass/surface/fill` | 48 lift and hairline rim, under surface/glass | The lift only, as a shadow | | `liquidGlass/surface/glass` | frost 16, refraction 0.7, depth 30 | No equivalent | | `liquidGlass/control/fill` | 15 lift and hairline rim, under control/glass | The lift only, as a shadow | | `liquidGlass/control/glass` | frost 6, refraction 0.7, depth 30 | No equivalent | | `liquidGlass/control-clear/fill` | 15 lift, lighter, and the same rim | The lift only, as a shadow | | `liquidGlass/control-clear/glass` | frost 2, refraction 0.7, depth 30 | No equivalent | | `liquidGlass/keyboard` | frost 8, refraction 0.8, depth 40 | No equivalent | The six shadows are declared once for the whole docs site as `--ap-shadow-xs` through `--ap-shadow-2xl`, and every specimen on this page reads those variables instead of repeating the numbers. In the file, dropShadow/xs is style key `S:73890325a32ce0113f288ae81d4e943f0cb39f93`, bound by effectStyleId on Accordion, all three Card sets and Segmented Control. The blurs and the glass are declared in no stylesheet at all, on purpose. --- --- title: Motion description: Durations, easing curves, spring configs and seven ready presets. Springs first on native, curves where CSS is the target. Motion decided once. group: Foundations source: https://www.appetiteui.com/docs/motion --- # Motion Five durations, five curves and three spring configurations. Seven presets alias them, so a sheet, a dialog and a toast move the same way in every screen and in the code. ## Duration Five steps and nothing between them. Click a row to run that one on its own. Figure: the motion tokens in group 0, played on a track. | Token | Value, ms | Used by | | --- | --- | --- | | `motion/duration/instant` | 0 | No preset | | `motion/duration/fast` | 120 | sheet exit, toast enter, toggle state | | `motion/duration/base` | 200 | sheet enter, dialog enter, page push | | `motion/duration/slow` | 320 | No preset | | `motion/duration/slower` | 480 | skeleton pulse | instant and slow are referenced by no preset. Every preset in the file reaches for fast, base or slower, which leaves those two for a component to name directly. ## Easing The same marker and the same duration, five different curves. Duration says how long, easing says where the time goes. Figure: the motion tokens in group 1, played on a track. | Token | Value | Used by | | --- | --- | --- | | `motion/easing/linear` | cubic-bezier(0, 0, 1, 1) | skeleton pulse | | `motion/easing/standard` | cubic-bezier(0.2, 0, 0, 1) | toggle state | | `motion/easing/decelerate` | cubic-bezier(0, 0, 0, 1) | sheet enter, toast enter | | `motion/easing/accelerate` | cubic-bezier(0.3, 0, 1, 1) | sheet exit | | `motion/easing/emphasized` | cubic-bezier(0.05, 0.7, 0.1, 1) | dialog enter, page push | decelerate and accelerate are a pair: a thing arriving slows into place, a thing leaving speeds away. standard handles small changes that stay put. emphasized is the steepest of the five off the line, roughly three quarters of the distance covered in the first fifth of the time, then a long settle, which is why it carries the dialog and the page push. ## Springs Three configurations, stated as response, damping and mass. Springs are not expressible in CSS, so they stay in the Figma file and in the native code output; the curves above are what reaches the web. | Token | Response | Damping | Mass | | --- | --- | --- | --- | | `motion/spring/subtle` | 0.30 | 0.90 | 1.0 | | `motion/spring/default` | 0.40 | 0.80 | 1.0 | | `motion/spring/bouncy` | 0.50 | 0.65 | 1.0 | subtle carries motion/toast/enter and motion/toggle/state. default carries motion/sheet/enter and motion/dialog/enter. bouncy is referenced by no preset, so it is there for a component that asks for it directly. Mass is 1.0 on all three; response and damping are the only two things that move. ## Presets Seven presets, every one an alias. A preset holds no value of its own, it points at a duration, a curve and, where the platform can spring, a spring. Figure: the motion tokens in group 2, played on a track. | Preset | Duration | Easing | Spring | | --- | --- | --- | --- | | `motion/sheet/enter` | base | decelerate | default | | `motion/sheet/exit` | fast | accelerate | None | | `motion/dialog/enter` | base | emphasized | default | | `motion/toast/enter` | fast | decelerate | subtle | | `motion/toggle/state` | fast | standard | subtle | | `motion/page/push` | base | emphasized | None | | `motion/skeleton/pulse` | slower | linear | None | motion/toggle/state drives the Segmented Control indicator and the Navigation bar pill on these pages, both measured at 120ms on cubic-bezier(0.2, 0, 0, 1). motion/dialog/enter drives the Alert overlay. There is no motion/dialog/exit. Sheet ships both directions, dialog ships one, so the enter pair currently runs in both directions on the Alert overlay. That is a gap in the file, not a decision, and it is named here rather than papered over. --- --- title: Platform Bridge description: Apple system colours and Material 3 roles carrying your brand, with one switch back to the untouched platform baseline. Not a screenshot of the OS. group: Foundations source: https://www.appetiteui.com/docs/platform-bridge --- # Platform Bridge The layer that puts your brand inside Apple's and Google's own design systems. 112 variables on two modes: Brand resolves a platform role to an Appetite UI token, System returns the platform's published value. One dropdown moves a whole screen. ## What it covers Appetite UI does not replace the Apple and Material design systems. It names their slots and points each one at a token you control. 112 slots, 52 from Apple and 60 from Material, covering colour, shape and type size. Nothing here redraws a component. It decides where a component reads its value from. | Slot group | Slots | Brand mode reads | System mode reads | | --- | --- | --- | --- | | iOS colour roles | 34 | Color Tokens, semantic | `platform/ios/*` | | iOS shape | 7 | `dimensions/radius/*` | Apple's own radii | | iOS type size | 11 | `typography/fontSize/*` | Dynamic Type at Large | | Material colour roles | 32 | Color Tokens, semantic | `platform/m3/*` | | Material shape | 6 | `dimensions/radius/*` | Material's own radii | | Material state opacity | 7 | a bare number | the same number | | Material type size | 15 | `typography/fontSize/*` | published typescale | A second collection, Platform, is easy to confuse with this one and does a different job. It holds 16 variables on three modes, ios-md, ios-lg and android, and it carries geometry rather than colour: min-touch-target 44, 44, 48; bar/top-height 44, 44, 64; control/corner 8, 8, 9999. The two iOS modes differ in exactly one variable, grid/safe-area/top at 59 and 62. ## Brand mode In Brand mode a platform slot stops being a colour and becomes a pointer. iOS/tintColor points at brand/base, which points at colors/blue/500, and that primitive is the only place the hex is written down. M3/sys/color/primary ends on the same primitive, so one edit moves an iOS screen and an Android screen together. Figure: the Platform Bridge chain, step 1. | Platform slot | Resolves through | Brand value | | --- | --- | --- | | `iOS/label` | | #0E0E0F | | `M3/sys/color/on-surface` | | #0E0E0F | | `iOS/systemRed` | | #FB2C36 | | `M3/sys/color/error` | | #FB2C36 | | `iOS/secondaryLabel` | | #48484A | | `iOS/separator` | | #E9E9EA | | `M3/sys/color/tertiary` | | #7D52F4 | 101 of the 112 slots resolve to an Appetite UI token in Brand mode. The other 11 hold a platform value or a bare number in both modes, and they are named under What does not move. ## System mode Switch the collection to System and every slot drops its pointer and returns the value Apple and Google publish. Nothing is approximated. Those values sit in Color Tokens as 67 untouched platform primitives, stored once and never retyped into a component. It is one switch on the frame, not a rebuild. The screen stops being yours and becomes theirs, which is what a native review asks to see. | Platform slot | Brand mode | System mode | Published as | | --- | --- | --- | --- | | `iOS/tintColor` | #155DFC | #007AFF | systemBlue | | `iOS/secondaryLabel` | #48484A | #3C3C43 at 60% | secondaryLabel | | `iOS/separator` | #E9E9EA | #3C3C43 at 18% | separator | | `iOS/systemGray` | #8E8E93 | #8E8E93 | systemGray | | `M3/sys/color/primary` | #155DFC | #6750A4 | Material baseline primary | A few slots land on the same value in both modes without being wired that way. The Appetite UI gray scale and Apple's systemGray already agree at #8E8E93. ## Typography The Bridge carries type, not only colour. 11 iOS text styles and 15 Material typescale steps each point at an Appetite UI step in Brand mode and return the platform's own published size in System mode. It carries the size step only, not weight, line height or letter spacing. | Step | Brand points at | Brand | System | | --- | --- | --- | --- | | `iOS/sys/typography/largeTitle` | `fontSize/4xl` | 36 | 34 | | `iOS/sys/typography/title2` | `fontSize/xl` | 20 | 22 | | `iOS/sys/typography/headline` | `fontSize/lg` | 18 | 17 | | `iOS/sys/typography/body` | `fontSize/lg` | 18 | 17 | | `iOS/sys/typography/subheadline` | `fontSize/sm` | 14 | 15 | | `iOS/sys/typography/footnote` | `fontSize/xs` | 12 | 13 | | `iOS/sys/typography/caption2` | `fontSize/xs` | 12 | 11 | | `M3/sys/typescale/display-large` | `fontSize/6xl` | 60 | 57 | | `M3/sys/typescale/headline-large` | `fontSize/3xl` | 30 | 32 | | `M3/sys/typescale/title-large` | `fontSize/xl` | 20 | 22 | | `M3/sys/typescale/label-small` | `fontSize/xs` | 12 | 11 | The Appetite UI scale is coarser than either platform ramp, so steps collapse in Brand mode. iOS headline and body both land on `fontSize/lg`, and footnote, caption1 and caption2 all land on `fontSize/xs`. Distinctions Apple draws a point apart do not survive the switch. ## What does not move Fourteen slots are wired to the same source in both modes. Eleven are the ones counted out of the 112 under Brand mode, and the switch cannot reach them. Three run the other way: they take an Appetite UI token like the other 101, then keep it. | Slots | Count | Value in both modes | | --- | --- | --- | | `M3/sys/state/*` | 7 | Bare numbers, not colours: hover 8, focus 10, pressed 10, dragged 16, disabled 38 and 12, scrim 32. | | `iOS/glassControl``iOS/segmentSelected``iOS/glassClear``iOS/glassBorderDark` | 4 | An Apple value in Brand mode too. The last is a bare white at 12%, written into the variable itself. | | `iOS/glassBorder``iOS/glassEdge``iOS/glassShadow` | 3 | An Appetite UI glass token in System mode too, because Apple publishes no value for them to return to. | Keeping the Appetite UI token in those three is the honest result, not a gap. --- --- title: Assets description: What ships in the file besides components: logos, illustrations, placeholder imagery and the brand marks you are allowed to change. group: Foundations source: https://www.appetiteui.com/docs/assets --- # Assets The stock artwork a mock up needs before it looks real. Logos, flags, emoji and store badges, all of it components, so one swap reaches every screen. Two of the 966 are ours. Use the rest to make a screen look finished, then replace them before you ship. ![The emoji set that ships with the file](/images/docs-assets-emoji.webp) ## Five families Four are component sets, the brand marks are loose components. Everything is a component, so a swap reaches every screen that uses it at once. | Family | Count | What it is | Owner | | --- | --- | --- | --- | | Appetite UI logo | 2 | Symbol and lockup, vector, fill bound to a colour variable | Ours | | Brand marks | 108 | 7 groups, 32 x 32 vector, one component each | Third party | | Country flags | 262 | One set, circular, 24 x 24 vector | Third party | | Emoji | 586 | One set, 24 x 24, raster fill, not vector | Third party | | Store badges | 8 | One set, 120 x 40 vector, 4 stores in 2 styles | Third party | 2 + 108 + 262 + 586 + 8 = 966, which is the component count on the page exactly, so nothing here is unaccounted for. The application screens hold another 34 photo slots, each an instance, so one photograph swaps into every screen at once. 964 of those 966 belong to somebody else. Every third party mark in the file carries the same component description: marks are owned by their respective companies, they must not be recoloured or redrawn, and you check the provider brand guidelines before you ship. Use them to populate a realistic mock up, then replace them. Two known slips, both on the fix list: the Appetite UI logo set carries that third party description too, and the Figma page header still reads 600+ emoji while the set and the licence page both say 586. --- --- title: Code output description: One W3C DTCG source generates CSS, SwiftUI, Jetpack Compose and Flutter. What the export contains, and how agents read the same source. group: Foundations source: https://www.appetiteui.com/docs/code-output --- # Code output One token source generates CSS, SwiftUI, Compose and Flutter. The same file is what a coding agent reads, so the name in the design and the name in the code are the same name. ## Preview Appetite UI holds its values once, as a token file, and generates the rest. One variable, four languages, no second copy to keep in step. Specimen: brand/base, from one variable to four generated names. 6 collections, 2 modes on color, 1 source file. The first three names are not chosen here. Every variable in the file stores its own code name for web, iOS and Android, and the export reads them off the variable rather than deriving them. ## The source One JSON file in the W3C Design Tokens format, run through Style Dictionary v5. Primitives hold the values. Semantic tokens point at primitives. Nothing holds a value twice. Code: The token file. | Collection | Variables | Modes | | --- | --- | --- | | `Foundations` | 196 | Default | | `Color Tokens` | 161 | Light, Dark | | `Typography` | 28 | Default | | `Motion` | 31 | Default | | `Platform` | 16 | ios-md, ios-lg, android | | `Platform Bridge` | 112 | Brand, System | Figma writes a path with slashes, `brand/base`. The token file writes the same path with dots, so the reference reads `\{colors.blue.500\}`. Same token, two notations, and the export is where they meet. ## Targets Four outputs off the one source. These are examples of what the export produces, cut to three tokens so the four sit side by side: a color, a spacing step and a duration. Code, in four languages: CSS custom properties, SwiftUI, Jetpack Compose, Flutter. The file has no slot for Dart, so the Flutter names above follow the same transform rather than coming out of the source. The other three are read off the variables. ## Modes A mode is a second value on the same token, not a second file. Light and Dark on color, Brand and System on the platform mirrors, three device modes on the platform metrics. One source carries all of them. | Token | Mode | Resolves to | Value | | --- | --- | --- | --- | | `brand/base` | Light | colors/blue/500 | #155DFC | | `brand/base` | Dark | colors/blue/400 | #2B7FFF | | `M3/sys/color/primary` | Brand | brand/base | #155DFC | | `M3/sys/color/primary` | System | platform/m3/primary | #6750A4 | Code: What the CSS export does with two modes. ## Agents The Appetite UI Core Skill is an instruction file included with your purchase. It teaches a coding agent which token and which pattern to use. Add it to your project and Claude, Cursor or anything else that reads project instructions works from the same source the export runs on. Figure: What a variable says about itself. Those descriptions are carried on the variables themselves, which is the whole trick: the guidance travels with the value instead of living in a document nobody opens. The Core Skill replaced Figma Code Connect, which is not available on our plan and was the wrong tool anyway. Code Connect maps one component to one snippet. What an agent is missing is not the snippet, it is knowing which token to reach for. --- --- title: Accordion description: Expand and collapse sections of content. The Accordion in Appetite UI for Figma: states, properties and the tokens that drive them. group: Components source: https://www.appetiteui.com/docs/accordion --- # Accordion Accordion lets people expand and collapse sections of content, so a long screen stays short until they ask for more. ## Preview A collapsible content row for FAQ lists and grouped settings. Closed it shows the title, open it adds the description underneath. Live specimens, open one. Specimen: Accordion, State Default and Active. 340 wide, 56 tall closed, padding 16, gap 12. Every row is one component with one hidden text node, not two rows swapped over. The description sits in the same Text frame as the title in all six variants and switches off while the row is closed, so the closed row hugs to exactly 56: 24 of title plus 16 of padding at each end. Opening reveals an element, it does not add one. Width is pinned at 340, only the height hugs. ## State Three states, and the radius carries the change. Default is a pill on bg/white-0 with a 1px border/soft-100 edge. Pressed drops the border, fills bg/weak-50 and rounds to 24. Active keeps that fill, rounds to 18 and opens the description. Specimen: State Default, Pressed, Active. radius full, then 24, then 18. Open, the row is 142 tall with the icon and 122 without. That 20 is the description wrapping in a narrower column, not a second spec. Default is the only variant carrying the 1px edge and the only one carrying shadows: a 2 blur one down at 4 percent and a 4 blur four down at 1. Pressed and Active are flat. The chevron is swapped, not turned, chevron-down while closed and chevron-up when open, a different icon instance rather than a transform. ## Icon A boolean, not a size change. With it on, a 24 box holding a 20 vector at stroke 2 leads the row and the text column narrows by 36, the icon plus the 12 gap. Specimen: Icon True, Icon False. 24 box, 20 vector, stroke 2, fg/sub-700. The chevron never leaves. It says the row opens, and it is the one part that changes colour with State: fg/soft-500 while closed, fg/sub-700 the moment you touch it or open it. The leading icon holds fg/sub-700 throughout. Both sit in a 24 box, but the leading glyph draws at 20 and the chevron at 12 by 6, so the same box carries different optical weight at each end of the row. ## Properties One variant axis, one boolean, two text properties and an instance swap. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Pressed \| Active | Default | | `Icon` | Boolean | true | | `Title` | Text | Title of accordion | | `Description` | Text, visible in Active only | Set in the file | | `Icon Swap` | Instance swap | circle-question-mark | Six variants, three states across the icon boolean, and the matrix is complete. Title and Description are both text properties, so a real FAQ row is two overrides. Description is bound to the node hidden while closed, so you can fill it on a collapsed instance and see nothing until State goes to Active. The title is Regular 16/24, the same weight as the description at 14/20, so size and colour are all that separate them. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Closed fill | | `border/soft-100` | #E9E9EA | #2F2F31 | The 1px edge, Default only | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed and Active fill | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Description, leading icon, chevron once open | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Chevron while closed | Radius runs `dimensions/radius/full`, then `2xl` at 24, then `xl` at 18. Full clamps to half the height on a 56 row, so you see 28, then 24, then 18: the row squares up slightly as it opens. Six colours, all semantic, none a raw primitive, and every one themes. The pair of shadows on the closed row is the only thing here that is neither colour nor geometry; it makes Default read as lifted and Pressed as pushed in. ## Guidelines - Do: Stack them. One row alone reads as a button that did nothing. The list is what says there is more behind each title. - Avoid: Do not nest one inside another. A reader two levels deep has lost track of what closing does. The component description names the native equivalents: DisclosureGroup on iOS, an expandable ListItem on Android. Both animate their own height on open and neither ships the lift this component draws, so the closed row's shadow is an Appetite UI decision, not a platform one. Keep the title to one line at 236, the column you get once the leading icon is on. The row hugs its content, and a wrapping title pushes the closed height past 56 and breaks the stack's rhythm. --- --- title: Action Sheet description: A bottom anchored list of actions for one object. The Action Sheet in Appetite UI for Figma: anatomy, properties, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/action-sheet --- # Action Sheet Action Sheet presents a short list of actions for one object, anchored to the bottom of the screen. ## Preview A bottom anchored list of actions about one object. The scrim blocks everything behind it, so the sheet is a decision, not a panel. Live specimen: dismiss it from the close button or the scrim. Specimen: Action Sheet, shipped content. 340 x 396, padding 24 20 20, gap 16, radius 32. The sheet is 396 tall by arithmetic, not by setting: 24 of top padding, then blocks of 60, 140 and 120 stacked 16 apart, then 20. It hugs, so a fuller slot grows the sheet and leaves padding and gaps alone. Enter and exit are not mirrors: in at 200 on decelerate, out at 120 on accelerate, both from the sheet preset in Motion foundations. ## Content The header block carries the message. Title 24/32 Semi Bold at minus 0.4 tracking, subheadline 16/24 Regular, 4 apart. The subheadline is a boolean; drop it and the sheet gives back 28. Specimen: Subheadline True, Subheadline False. block 300 x 60, then 300 x 32. One line of subheadline is what the 60 assumes. Two lines make the block 84 and the sheet 420. Expected, not broken. ## Properties No variant axis. Two text properties, one boolean and one slot. | Property | Type | Default | | --- | --- | --- | | `Title` | Text | Title of action sheet | | `Subheadline` | Boolean | true | | `Subheadline Text` | Text | Subheadline of action sheet | | `Content Slot` | Slot | Empty | Action Sheet ships one variant. The two actions are Button instances, so type, style, size and state live on Button. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Sheet fill | | `border/soft-100` | #E9E9EA | #2F2F31 | The 1px inside edge | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Subheadline | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Close icon | | `brand/base` | #155DFC | #2B7FFF | Filled fill, Link label | | `bg/scrim` | #000000 at 40% | #000000 at 48% | The scrim | Radius is `dimensions/radius/3xl` at 32 on the sheet and `full` on every button, the close included. The shadow is `dropShadow/2xl`, black at 12 percent and the same in both themes. ## Guidelines - Do: Put the destructive action last and give it the error tokens. A red row that always sits in the same place is a row people learn to look for. - Avoid: Do not stack four equal actions. A sheet settles one decision about one object. A list of everything you can do is a menu, and a menu goes in the slot. iOS ships this pattern natively. On Android reach for a Modal Sheet instead. --- --- title: AI description: AI components for mobile apps, from the Appetite UI design system for Figma. Prompt entry, streaming replies and the states a model makes you handle. group: Components source: https://www.appetiteui.com/docs/ai-component --- # AI Two components, Message and Prompt composer. Between them they cover a thread, an answer that is still arriving, an answer that failed, and the five states of the input underneath. Both are mobile width and both read from the same tokens as everything else. ## Preview Two surfaces an AI product cannot borrow from anything else in the system: the conversation row and the prompt input. Everything standing around them, suggestion chips, source cards, the tools menu and the model picker, is Chip, Card, Context Menu and Dropdown. Specimen: Message Role User, Message Role Assistant, Prompt Composer State Empty. all three 361 wide, 72, 96 and 88 tall. The two sets never meet inside the file. A thread is a column of Message rows and the composer is pinned under it, so neither knows what the other holds. The exchange above is the copy the file's own example screen carries, not the component defaults. ## Message One conversation row. Role decides the shape, State decides what an unfinished answer looks like. Both are on the same set, so a thread is one component repeated. Specimen: Role User and Role Assistant, both State Default. 361 x 72 and 361 x 48, bubble 280 at radii 20 20 6 20. | Property | Values | What it changes | | --- | --- | --- | | `Role` | User, Assistant | User draws a 280 bubble in bg/soft-100 pushed right. Assistant draws no container and runs the full 361. | | `State` | Default, Streaming, Error | Streaming hangs a 28 x 6 row of three dots under the answer. Error swaps it for a 12 radius panel with an icon, a reason and a retry. | | `Message` | Text | The body copy, and the only text property the set exposes. | The grid is deliberately sparse: 4 of the 6 Role by State cells exist, because a user turn cannot stream and cannot fail. Two things do not line up. The Answer frame uses gap 8 in Default and gap 12 in Streaming and Error, and the Actions frame under the error carries a hardcoded 30 of left padding to line the button up with the copy rather than with the icon. ## Prompt composer The input everything else is arranged around. One shell, five states, and the state is the surface being honest about what it is doing: waiting, ready, working, carrying a file, or shut. Specimen: State Empty. 361 x 88, radius 24, 1px inside stroke, dropShadow/sm. Empty still draws the send button, dormant in fg/disabled-400, rather than hiding it. Typing: Send turns brand/base as soon as the field is not empty. Generating: Send is replaced by Stop on the same disc. Disabled: offline and over limit, every glyph fg/disabled-400. Attachment: the file sits above the prompt with its own remove. The plus opens the tools menu, a Context Menu instance, so the tool list is not fixed here. Padding is not symmetric: 12 top and left, 10 right and bottom, which centres a 32 disc in the corner. ## Anatomy Measured in the file, not rounded to a scale. Where the file sits off its own spacing scale it is written here as it stands. | Part | Measured | How it resolves | | --- | --- | --- | | User bubble | 280 x 72, padding 12 and 16, radii 20 20 6 20 | Fixed 280 in a 361 row aligned right. 16 + 248 + 16 = 280, and the clipped corner is the near one. | | Assistant answer | 361 wide, 48 at two lines | No frame, no fill. The Answer stack is gap 8 in Default and gap 12 in Streaming and Error. | | Error panel | 361 x 112, padding 12, radius 12 | Row 337 x 40 at gap 10, then a 100 x 36 button. Actions carries 30 of left padding, off the scale, to align the button with the copy. | | Composer shell | 361 wide, padding 12 10 10 12, radius 24, gap 10 | Content is 339 in every state. Heights run 88, 112, 112, 112 and 175. | | Controls row | 339 x 32, gap 8, tools gap 6 | Discs are 32 at radius 999 holding an 18 glyph. Stop is an 11 x 11 rectangle at radius 2.5. | | Attachment row | 339 x 53, padding 8 10 8 8, radius 14 | Tile 36 at radius 10, remove 20 at radius 10, meta gap 1. 8 + 36 + 10 + 245 + 10 + 20 + 10 = 339. | Both 1px strokes are drawn INSIDE, so a CSS border would make the shell 363 and the attachment row 55. They are reproduced as inset shadows, which add nothing. ## Tokens Nothing on either component carries a raw colour. Values are the light resolution; each has a dark counterpart inside the same variable. | Where | Token | Light | | --- | --- | --- | | Composer surface | `bg/white-0` | #FFFFFF | | Bubble, discs, tile | `bg/soft-100` | #E9E9EA | | Disabled shell, retry | `bg/weak-50` | #F4F5F5 | | Remove control | `bg/sub-200` | #D1D1D6 | | Both 1px strokes | `border/soft-100` | #E9E9EA | | Answer, bubble, file name, x | `text/strong-950`, `fg/strong-950` | #0E0E0F | | Retry label, plus glyph | `text/sub-700`, `fg/sub-700` | #48484A | | Placeholder, meta, dots | `text/soft-500`, `fg/soft-500` | #8E8E93 | | Dormant and disabled glyphs | `fg/disabled-400` | #AEAEB2 | | Send and Stop disc | `brand/base` | #155DFC | | Glyph on that disc | `text/on-color/strong` | #FFFFFF | | Error panel, icon, copy | `state/error/lighter-50`, `base-500`, `dark-950` | #FEF2F2 #FB2C36 #460809 | In dark mode bg/white-0 becomes #0E0E0F and text/strong-950 becomes #FFFFFF, so surface and copy swap ends of the ramp together. The error tint is the one that does not simply invert: #FEF2F2 becomes red at 16 percent alpha. ## Guidelines One rule the set already decided for you, and two it cannot enforce. - Do: Let the assistant run as plain text on the full 361. The reader scans one column and a long answer is not squeezed into a 280 bubble. - Avoid: Do not give the assistant a bubble as well. Two bubbles read as two people, and the answer loses the width it was built for. The set carries no Streaming and no Error on the User side, so a failed send is not a Message state: handle it on the composer or as a Toast. Keep Generating reachable, because Stop is the only control on either component that ends a running answer. And the composer never grows on its own: its height follows the value, so decide the ceiling yourself. --- --- title: Alert description: A dialog that stops the flow to say what happened and offer the way out. The Alert in Appetite UI for Figma: 4 types, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/alert --- # Alert Alert is a dialog that stops the flow to say what happened and, when it matters, what to do about it. ## Preview A centred dialog that holds the screen until it is answered. Not a toast, which passes on its own, and not an inline message, which stays in the flow. Pair it with an overlay. Dismiss it from either button or the scrim, then bring it back. Specimen: Alert, Type Error. 340 x 288, padding 24 16 16, gap 24, radius 24. 288 is arithmetic, not a setting: 24 of top padding, a 172 icon block, 24, a 52 button block, 16. The card hugs, so a longer message grows it and leaves the padding and the gap alone. In and out both run 200 on the emphasized curve, the dialog preset from Motion foundations; the file names no dialog exit. ## Type One axis, four values. The disc takes the type's base-500, the icon changes with it, and the right hand action follows: Error goes red, the other three stay brand. Size, copy and layout hold. Specimen: Type Error, Success, Warning, Verified, in that order. 48 disc in a 56 halo, 28 icon, stroke 2.25. Warning and Verified send you forward in brand blue, only Error colours its own action. That asymmetry is in the file, not a choice made here. One thing the docs do not reproduce: Verified's mark is not a disc in the file. Node 125:609 draws two vectors at 54.34 and 46.34 where the other three carry a 56 and a 48 ellipse. Drawn here as a disc and flagged, not redrawn from a path nobody read. ## Properties One variant axis and two text properties. No boolean, no slot, no size. | Property | Type | Default | | --- | --- | --- | | `Type` | Error \| Success \| Warning \| Verified | Error | | `Message` | Text | Update available | | `Alert Text` | Text | Set in the file | Alert ships 4 variants, one per type. The two actions are Button instances at Size md Style Link, so their type, size and state live on Button. The right hand label is Install on all four; only its colour changes with the type. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Card fill and the halo behind the disc | | `border/soft-100` | #E9E9EA | #2F2F31 | The 1px inside edge and the divider | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Body and the Dismiss label | | `state/error/base-500` | #FB2C36 | #FB2C36 | The disc, and the right action on Error only | | `brand/base` | #155DFC | #2B7FFF | The right action on the other three | | `static/static-white-0` | #FFFFFF | #FFFFFF | The icon inside the disc | Success is `#22C55E` then `#16A34A`, Warning `#FF6900` then `#F54900`, Verified `#47C2FF` then `#35ADE9`. Radius is `dimensions/radius/2xl` at 24 on the card and `full` on the disc and both buttons. ## Guidelines - Do: Dismiss on the left, the action on the right. Both are Link buttons, so neither shouts, and the colour on the right is the only thing that says which way is forward. - Avoid: Do not swap them. The way out sits on the left in every alert, so the same thumb movement never means two different things. An alert waits. Something that passes on its own is a Toast, and something that asks a question is a Modal. --- --- title: Avatar description: A person or entity as a photo, initials or an icon. The Avatar in Appetite UI for Figma: sizes, badges and the tokens behind each one. group: Components source: https://www.appetiteui.com/docs/avatar --- # Avatar Avatar represents a person or an entity as a photo, initials or an icon. ## Preview A user or an entity, always circular. Type Photo at 6xl with a presence dot. The file ships twelve named portraits; the docs run one picture across every Photo specimen. Specimen: Avatar, Type Photo, Size 6xl. 80 px, radius full, badge 32. ## Size Nine sizes and the disc is the only thing the axis sets. The acronym ramp follows it, and so does the badge box, which is why the two smallest sizes have almost no room left for one. Specimen: 6xl, 5xl, 4xl, 3xl, 2xl, xl, lg, base, sm. 80, 72, 64, 56, 48, 40, 32, 24, 20. Badge box per size: 32, 28, 28, 24, 20, 18, 16, 12, 12. Two initials survive down to xl, then the file drops to one, because 14 over a 32 disc is all that fits. Ramp 30/36 and 24/32 at minus 0.4, then 18/28 and 16/24, then 14/20 and 12/16 at 0.1. ## Type Three ways to fill the disc. Photo takes an image. Symbol and Acronym are the fallbacks, and both sit on the same gray/200 disc, so a list of users never breaks its rhythm when one of them has no picture. Specimen: Type Photo, Symbol, Acronym. shown at 4xl, 64 px. The symbol is two white ellipses clipped by the disc, not an icon: head 40 percent of the disc, body 80 by 60, which is the 32 and the 64 by 48 the file draws inside its 80 frame. Both scale with the circle rather than sitting at a fixed icon size. The photo is cropped the same way, by percentage, so one rule serves all nine sizes. ## Status Two independent booleans, one badge each. Top carries an action or a verification, bottom reports presence. Both wear a ring in the card colour so they read against any photo. Specimen: Top Status: Verified, Add, Check, Delete. ring 87.5, fill 75, icon 37.5 percent of the box. Specimen: Bottom Status: Online, Offline, Busy, Away. ring 62.5, dot 37.5 percent of the box. Only Verified breaks the circle. The file draws it as an eight point seal, one rounded square over the same square turned 45, and the ring behind it follows the same outline. The presence row runs on the photo, which is where the ring earns its keep: a green dot on a light shoulder needs the card colour behind it to stay a dot. ## Properties Three variant axes and two booleans. The name axis is twelve people, which is what makes the set 324 variants wide. | Property | Type | Default | | --- | --- | --- | | `Size` | 6xl \| 5xl \| 4xl \| 3xl \| 2xl \| xl \| lg \| base \| sm | 6xl | | `Type` | Photo \| Symbol \| Acronym | Photo | | `Avatar` | Twelve names | Erik Holmberg | | `Top Status` | Boolean | false | | `Bottom Status` | Boolean | false | The badges are their own sets: `_Avatar Status - Top` carries Verified, Add, Check and Delete, `_Avatar Status - Bottom` carries Online, Offline, Busy and Away. Avatar group is a third set with three variants and is not on this page yet. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `colors/gray/200` | #D1D1D6 | #D1D1D6 | Symbol and Acronym disc | | `colors/gray/900` | #242427 | #242427 | Acronym label | | `static/static-white-0` | #FFFFFF | #FFFFFF | Symbol glyph and badge icons | | `bg/white-0` | #FFFFFF | #0E0E0F | The ring behind every badge | | `state/success/base-500` | #22C55E | #16A34A | Online, and the Check badge | | `state/error/base-500` | #FB2C36 | #FB2C36 | Busy, and the Delete badge | | `state/away/base-500` | #FE9A00 | #E17100 | Away | | `state/faded/base-500` | #8E8E93 | #636366 | Offline, and the Add badge | | `state/verified/base-500` | #47C2FF | #35ADE9 | Verified | The disc and its label are primitives, not semantic tokens, so they hold the same two values in dark as in light. Everything else on the component switches. Radius is `dimensions/radius/full` on the disc and on every badge. ## Guidelines - Do: One badge at a time, and give it the corner that fits its job. Presence belongs at the bottom, an action or a mark belongs at the top. - Avoid: Do not hang badges on the small sizes. At 20 the badge box is 12, more than half the disc, and two of them leave nothing of the avatar to read. With no image the component falls back to initials, which is why Symbol and Acronym share the disc a photo would fill. --- --- title: Badge description: Small labels for status, category and count. The Badge in Appetite UI for Figma: types, states and the tokens behind every colour. group: Components source: https://www.appetiteui.com/docs/badge --- # Badge Badge is a small label that marks status, category or count next to the thing it describes. ## Preview A compact status or count marker that attaches to something else. Dot for presence, Circle for a count, Pill for a label. Specimen: Badge, Type Pill, Circle and Dot. 25 x 20, 20 x 20, 6 x 6, radius full. Three types, one shell. Pill and Circle share one inner wrapper, a 12/16 Regular label padded 4 either side, and differ only in outer sizing: Pill hugs the wrapper, Circle is pinned to 20 square. Dot has no children, just a 6 by 6 filled box. The text property does nothing on a Dot, and a Circle holding three digits pushes past its edge. ## Type Pill hugs its label, Circle is pinned square at 20, Dot drops the label for a 6px mark. All three take radius full. Specimen: Pill, then Circle, then Dot, a row each. 25 x 20, 20 x 20, 6 x 6, label 12/16 Regular at padding 2 and 6. The pill's 25 is arithmetic, not a set width: 2 outer, 4 wrapper, a 13 wide label, the same back out. Circle's box is pinned to 20 while the wrapper inside measures 21. Two digits fit, three do not, so a count badge caps at 99 and prints a plus. Dot keeps padding 6 and 8 from when it was a shrunken pill; with no children it does nothing. ## State Ten states, each mapped to a token family, not a colour, in both styles. Color fills base-500 with a static white label, white in both themes. Soft fills light-200 with a dark-950 label, an alpha tint in dark, so the badge stays translucent. Neutral reads no state family: bg/strong-950 filled, bg/soft-100 soft. Specimen: Neutral, Information, Error, Warning, Away, Success, Feature, Verified, Highlighted, Stable. Color, Soft and Dot in every cell. Cells read in the order named above, five to a row. Neutral is not a state family, and it is the only badge whose Color fill and label invert with the theme: it prints text/white-0, which flips to #0E0E0F in dark, giving a white pill with dark text. The nine coloured states print static/static-white-0 and hold white on their fill in both modes. ## Properties Three variant axes and one text property. Dot ignores the text, it has no label to put it in. | Property | Type | Default | | --- | --- | --- | | `State` | Neutral \| Information \| Error \| Warning \| Away \| Success \| Feature \| Verified \| Highlighted \| Stable | Neutral | | `Type` | Pill \| Circle \| Dot | Pill | | `Style` | Soft \| Color | Color | | `Badge Number` | Text, Pill and Circle only | 12 | Badge ships 60 variants: 10 states by 3 types by 2 styles, no holes. That is the second largest complete matrix in the library, behind Button at 288. Badge Number is the only content property, and it reaches Pill and Circle. A Dot has no text node, so setting it there does nothing. No size axis, no icon: a badge is 20 tall or 6 across, everything else is colour. ## Tokens Four roles, read by nine of the ten states from their own family. Success stands in for all nine, resolved to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `state/success/base-500` | #22C55E | #16A34A | Color fill | | `static/static-white-0` | #FFFFFF | #FFFFFF | Color label, white in both themes | | `state/success/light-200` | #BBF7D0 | #22C55E at 24% | Soft fill, a tint in light and an alpha in dark | | `state/success/dark-950` | #09361B | #86EFAC | Soft label | | `bg/strong-950` | #0E0E0F | #FFFFFF | Neutral Color fill | | `bg/soft-100` | #E9E9EA | #48484A | Neutral Soft fill | Radius is `dimensions/radius/full` on all three types. Swap success for any of the other eight families and the four roles hold: base-500 filled, static white on it, light-200 soft, dark-950 on that. Neutral is the exception, reading bg and text because there is no state/neutral family. In dark, light-200 is an alpha, not a solid, so a soft badge sits translucent over whatever it is pinned to. ## Guidelines - Do: Pick the type by what you have to say. A dot means something is there, a circle how many, a pill what it is. - Avoid: Do not run a sentence through a badge, or stack three of them. At 20 tall it marks something else, it is not a label of its own. The component description names the native homes: a badge on a tab item or a label on iOS, Badge on Android. Both expect it attached to something. Pin it to the thing it describes; do not let it float in a row of its own. If what you have to say is a sentence, use a Chip or an Alert, not a 20 tall marker carrying a 12 line. --- --- title: Breadcrumb description: Shows where a screen sits in a hierarchy. The Breadcrumb in Appetite UI for Figma: levels, states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/breadcrumb --- # Breadcrumb Breadcrumb shows where the current screen sits in a hierarchy, and takes people back up it. ## Preview A hierarchy trail. Three levels, the last one active. Rare on mobile by the file's own description, worth it only when a drill-down runs deeper than Back can explain. Specimen: Breadcrumb, Quantity 3. 388 x 20, gap 8, item gap 6. The last item is the page you are on, so it takes the strong tokens and stops being a link. Everything before it stays sub. ## Quantity Two to five levels. The track hugs, stays 20 tall and adds 140 for every level past the first: eight of gap, the sixteen wide chevron, eight again, then the 108 item. Specimen: Quantity 2, 3, 4, 5, top to bottom. 248, 388, 528, 668 wide, 20 tall. 668 does not fit a 390 screen, or this column. The track scrolls in place instead of wrapping, because a breadcrumb on two lines stops reading as one line of descent. ## Item The building block behind every level. One state axis and two booleans, so a level can be an icon and a label, a label alone, or an icon alone. Specimen: State Default, State Active, text only, icon only. 108 x 20, gap 6, icon 20 box on a 15 vector. Default paints fg/sub-700 and text/sub-700, Active paints fg/strong-950 and text/strong-950. Icon and label always carry the same value, so one rule moves both. The chevron stays fg/soft-500 at every level. Text false is the usual first level. ## Properties One axis on the published component. The rest sit on the item inside it, a building block not meant for direct use. | Property | Type | Default | | --- | --- | --- | | `Quantity` | 2 \| 3 \| 4 \| 5 | 5 | | `State` | Default \| Active, on the item | Default | | `Icon` | Boolean, on the item | true | | `Text` | Boolean, on the item | true | | `Edit Text` | Text, on the item | Breadcrumb | Breadcrumb ships 4 variants, one per quantity. The item ships 2, one per state, and is composed into all four, so a change there reaches every level of every trail. ## Tokens Three values and nothing else. No fill, no border, no radius anywhere in the component. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `text/sub-700` | #48484A | #C7C7CC | Every level that is not the current one, label and icon | | `text/strong-950` | #0E0E0F | #FFFFFF | The active level, label and icon | | `fg/soft-500` | #8E8E93 | #AEAEB2 | The chevron between levels | The file names the icon halves separately, `fg/sub-700` and `fg/strong-950`, and both resolve to the same primitive as their text counterpart in both modes. ## Guidelines - Do: Save it for a drill-down deep enough that Back stops answering the question. Drop the label on the root and let the icon carry it. - Avoid: Two levels do not need a trail. One step back is a Back button, and a breadcrumb there spends a whole row saying what the header already said. The component's own description is blunt about it: rare on mobile, only for deep drill-downs. --- --- title: Button description: Weight tells people which action matters most. The Button in Appetite UI for Figma: 4 styles, 3 sizes, every state and the tokens each one reads. group: Components source: https://www.appetiteui.com/docs/button --- # Button Button performs an action. Its weight tells people which action on the screen matters most. ## Preview Brand, filled, large. The icon slots hold the component's default circle placeholder. Specimen: Type Brand, Style Filled, Size lg. 56 px, radius full. ## Type and style Two axes, not one. Type carries the meaning, style carries the weight. Twelve combinations, all from the same geometry. Specimen: Type Brand, Neutral, Error by Style Filled, Outline, Weak, Link. Size md. ## Size Fixed heights so buttons line up with fields and list rows on the same screen. All three clear the 44 pt target once the tap area is applied. Specimen: Size. 36 / 44 / 56 px. ## State Four states ship with every combination. Hover the buttons to see the pressed fill. Specimen: State. Brand, Filled, md, in Default / Pressed / Loading / Disabled. ## Icon only Set Icon Only to True and the label wrapper is dropped. The button becomes a circle at the same height. Specimen: Icon Only, True. 36 / 44 / 56 px. ## Properties The Figma component properties, named exactly as the right hand panel names them. | Property | Type | Default | | --- | --- | --- | | `Type` | Brand \| Neutral \| Error | Brand | | `Style` | Filled \| Outline \| Weak \| Link | Filled | | `Size` | sm \| md \| lg | lg | | `State` | Default \| Pressed \| Loading \| Disabled | Default | | `Icon Only` | True \| False | False | | `Icon Left` | Boolean | true | | `Icon Right` | Boolean | true | | `Icon Left Swap` | Instance swap | circle | | `Icon Right Swap` | Instance swap | circle | | `Text` | Text | Button | 288 variants with no holes: three types by four styles by three sizes by four states by icon only. That is rare here. Chip ships 30 of its 40, Progress Steps 12 of 15. State has no Hover, the right call on a touch component. The family does not share that vocabulary: Compact Button carries Focused and no Loading, Quick Action Button carries neither, and the Floating Action Button's State axis is Open and Close, which are not states at all. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. Change one and it changes in Figma, the CSS, and all three native outputs. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | Filled fill, Outline and Link label | | `static/static-white-0` | #FFFFFF | #FFFFFF | Label on filled | | `bg/white-0` | #FFFFFF | #0E0E0F | Outline fill | | `brand/alpha-10` | #2B7FFF at 10% | #2B7FFF at 10% | Weak fill | | `bg/strong-950` | #0E0E0F | #FFFFFF | Neutral filled fill | | `state/error/base-500` | #FB2C36 | #FB2C36 | Error filled fill | | `bg/soft-100` | #E9E9EA | #48484A | Disabled fill | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Disabled label | | `brand/dark` | #193CB8 | #82BFFF | Pressed fill | | `dimensions/radius/full` | 9999 | 9999 | Corner radius, every size | One of these does not follow the theme. `static/static-white-0` is white in both modes, so a label on a brand fill stays white on a dark screen. `brand/dark` is a semantic alias, not a raw primitive, so the pressed fill moves with the brand: a step darker than brand/base in light and a step lighter in dark. ## Guidelines - Do: One filled button per screen. Pair it with a link so the destination of the eye is never in doubt. - Avoid: Error is for destructive actions, not for the secondary choice. Two filled buttons make the safe path and the risky one equally loud. Size is a visual scale, not a promise about the target. Give every button a 44pt hit area on iOS and 48dp on Android in code, whichever Size you pick; nothing in the file enforces it. The same Figma page ships four more button components, documented below in the order they appear there: Quick Action Button, Social Button at 128 variants, Compact Button and the Floating Action Button. ## Quick Action Button A circle icon with a label under it, for shortcut grids and the action row on a wallet or banking screen. Five semantic colours, three states. Not a substitute for Button. Specimen: Variant Brand, Neutral, Success, Warning, Error. 56 x 78, circle 56 at radius 24, icon 22, label 12/16. Specimen: State Default, Pressed, Disabled, on Variant Brand. brand/lighter, brand/light, bg/weak-50. Variant moves the container fill and the icon together and leaves the label alone, text/strong-950 in all five. Pressed steps each tint up one stop, lighter to light, and leaves the icon where it was. Disabled flattens the axis: every colour lands on bg/weak-50 with a `fg/disabled-400` icon, so a disabled row tells you nothing about the actions. Icon and label are both properties, so the five colours are the only thing an instance cannot override. ## Social Button Third-party sign-in for four providers, at one fixed height, so the stack under a login form stays even whichever set you ship. Never recolour the brand marks. Specimen: Company Google, Facebook, Apple, X at Style Filled. 245 x 56, radius full, padding 0 and 16, gap 8, label 16/24 Semi Bold. Specimen: Style Filled, Outline, Weak, Link at Company Google. bg/strong-950, bg/white-0 with a 1px border, bg/weak-50, no fill. Style changes the shell and never the mark. Google keeps its four colours in every style, Facebook stays on its own blue until Filled paints the mark white, and Apple and X flip between fg/white-0 and fg/strong-950. Link is still a 56 tall button with 16 of padding, not a text link. Facebook Filled is a raw #1877F2 rather than a token, the only fill in the set that does not theme. Disabled recolours all four Google vectors to fg/disabled-400, against the component's own rule. The label ships as a placeholder, so override it on every real instance. ## Compact Button An icon-only control at 32, for toolbars, image overlays and anywhere a full button would crowd the row. Three shells, four states. Specimen: Style Outline, Transparent, White at State Default. 32 square, radius full, icon 20 at stroke 2.5. Specimen: State Default, Pressed, Focused, Disabled, on Style Outline. bg/white-0, bg/soft-100, bg/white-0 under brand/base, bg/weak-50. The Style axis mostly exists in Default. Pressed paints all three bg/soft-100, Focused keeps each style's own fill under a 2px brand/base stroke and the focus ring, and Disabled paints Outline and White bg/weak-50 while Transparent stays clear, so the three Pressed variants are drawn identically, and so are Outline and White in Focused and in Disabled. The Outline border is present in Default and gone in every other state. Thirty-two is smaller than a finger, so give it a 44pt hit area on iOS and 48dp on Android in code. ## Floating Action Button One screen-level primary action, pinned above the content, with an expanded stack behind it. Android-native. On iOS, use a toolbar action instead. Specimen: State Close. 56 square, radius full, brand/base, plus at 24. Specimen: State Open. four actions, 16 from the button, 12 apart, label 16 from its circle. Close is one 56 circle. Open turns that circle neutral weak with an x and hangs four expanded actions above it, each a 56 brand circle with its label to the left. The stack sits outside the component's own 56 by 56 frame, which does not clip, so an instance measures 56 and paints 124 by 332. The labels are static/static-white-0, white in both modes, which is why the specimen above sits on a panel the component does not ship. State is the only property, so all four actions carry the same placeholder circle and the same word until you override each one. --- --- title: Card description: Groups related content and actions into one surface. The Card in Appetite UI for Figma: layouts, sizes and the tokens that hold them together. group: Components source: https://www.appetiteui.com/docs/card --- # Card Card groups related content and actions into a single tappable surface. ## Preview Vertical Card, default size and state. A 4px frame runs around the image, so the picture sits inside the card's rounding instead of fighting it. Specimen: Vertical Card, Size Default. 340 wide, radius 32, frame 4, image corners 28 and 6. The card is padded 4 on every side and the image is a rectangle inside it, so the card's own rounding clips the picture and the corner reads 28 inside 32. The image also carries a 0.75 hairline in black at 10 percent, which stops a white photograph bleeding into a white card. Both are easy to lose in a rebuild. ## Type Three sets, one shell. Vertical leads with the image, horizontal turns it on its side for a list, icon drops the image for a 40px tile. Border, radius, frame and content rhythm never change. Specimen: Vertical, Horizontal, Icon. same 340 shell, radius 32, content gap 4. Three separate sets, not one component with a Type axis, so an instance cannot be switched from vertical to horizontal. They share the shell, the property list and the badge, a real Badge instance at State Success and Type Pill, not a shape drawn into the card. They do not share the title ramp: vertical runs 24/32 down to 18/28, horizontal 20/28 down to 18/28, icon 24/32 down to 20/28. ## Size Compact is not a scaled copy. The vertical and icon cards narrow to 240 and drop padding to 12; the horizontal card keeps 340 and shrinks its image from 140 to 100. The title and the subtitle both step down with it. Specimen: Size Default, Size Compact. 340 / 240, padding 16 / 12, title 24 / 18, subtitle 14 / 12. Compact restructures the horizontal card. Default stacks the rating above the meta line, Compact puts them on one row at gap 4, so the card loses a line and 40 of height, 148 down to 108. That is the only place in the set where two sizes carry different content structure. On the icon card Size also moves the tile 40 to 36, the glyph 24 to 20 and the stroke 2.4 to 2. Every other icon in the set is stroke 2. ## State Two states. Pressed swaps the fill to bg/weak-50 and drops both shadows, so the card reads as pushed into the page. It does not move, scale or dim. Specimen: State Default, State Pressed. fill bg/white-0 then bg/weak-50, shadows off. That holds for the vertical and horizontal cards. The icon card does one more thing. Default draws a 24 icon on a 40 square at radius 10 in bg/weak-50; Pressed removes the tile and draws the glyph bare. The card measures 180 in Default and 164 in Pressed, so it jumps 16 shorter under a finger while the other two hold their height. Compact does not do it, both its states are 160. ## Properties The same property set on all three cards, except the image switch, which the icon card replaces with an instance swap. | Property | Type | Default | | --- | --- | --- | | `Size` | Default \| Compact | Default | | `State` | Default \| Pressed | Default | | `Image` | Boolean, vertical and horizontal only | true | | `Icon` | Instance swap, icon card only | circle | | `Subtitle` | Boolean | true | | `Rating` | Boolean | true | | `Description` | Boolean | true | | `Badge` | Boolean | true | | `Title Text` | Text | Title | | `Subtitle Text` | Text | Subtitle | | `Description Text` | Text | Meta · Info | Vertical Card 4 variants, Horizontal Card 4, Icon Card 4, each Size by State with no holes. Every content row is a boolean, so a card with no rating loses the row and the height follows. One exception: the Vertical Card at Size Default is the only variant in the set with a fixed height, 323, where the other eleven hug their content. Switch a row off there and you get whitespace, not a shorter card. Compact hugs. ## Tokens Every value the card reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | The card fill | | `border/soft-100` | #E9E9EA | #2F2F31 | The 1px edge | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed fill, the image slot and the icon tile | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Subtitle and rating | | `text/soft-500` | #8E8E93 | #AEAEB2 | Meta line | | `brand/base` | #155DFC | #2B7FFF | The icon in the tile | | `state/success/lighter-50` | #F0FDF4 | #22C55E at 16% | Badge fill | | `state/success/dark-950` | #09361B | #86EFAC | Badge label | | `colors/yellow/400` | #FFB900 | #FFB900 | The rating star, in both modes | Radius 32 is `dimensions/radius/3xl`. The image corners are 28 and 6, that radius minus the 4px frame. One of the ten values does not theme: `colors/yellow/400` is a single mode primitive, so the rating star holds #FFB900 on a dark card, the same call the Rating component makes. The two shadows are the pair the Accordion also carries, 0 1 2 at black 4 percent and 0 4 4 at black 1, and Pressed clears both. ## Guidelines - Do: Switch off the rows you have no data for. Every content row is a boolean, so a card without a rating loses the row rather than printing an empty one. - Avoid: true Pick the set by what the person is choosing on. Vertical when the picture is the reason to tap, horizontal when the list has to stay short, icon when an image would carry no meaning. The component descriptions name the native homes: a LazyVGrid card or a List row on iOS, an elevated or horizontal Card on Android. --- --- title: Checkbox description: Select one option, several, or none. The Checkbox in Appetite UI for Figma: every state, label handling and the tokens behind them. group: Components source: https://www.appetiteui.com/docs/checkbox --- # Checkbox Checkbox is a form control for selecting one option, several, or none. ## Preview Default, active. The box is 28px inside a 44px target, so the thing you can hit is bigger than the thing you can see. Click it. Specimen: State Default, Active True. 28 px box, radius 10, in a 44 px target. The 44 frame is the target and none of it is drawn. Inside it sit two rectangles: a 28 at radius 10 and, when the box is unchecked, a 24 at radius 8 in bg/white-0 centred on top. The 2px ring is what is left of the bigger one. The component has no auto layout, so the parts hold fixed coordinates, and the tick is a 16 icon whose path measures 10.67 by 7.33. ## State Three states across two active values. Pressed is not a darker shade: an unchecked box fills with brand while the finger is down, so the target confirms the touch before the value changes. Specimen: State x Active. Default, Pressed, Disabled, each off then on. Six variants, State by Active, no holes. Pressed on an unchecked box fills the ring with brand/base, the same value a checked box carries, so for the length of a touch only the tick tells the two apart. Disabled unchecked drops the white square, so where every other unchecked state reads as a ring, that one is a solid grey block. A disabled control is not meant to read as clearly as a live one. ## Label The label is its own component, so the box stays a box. Align puts it on either side, and the description can be switched off. All three tick. Specimen: Align Left, Align Right. 340 x 48, gap 16, title 18/28, description 14/20. The row is 340 by 48: a 44 control, 16 of gap, 280 of text. The height comes from the text, not the control, 28 for the title at 18/28 Semi Bold plus 20 for the description at 14/20, so switching Show Description off takes the row to 28 and leaves the 44 box overhanging it. Align swaps the order of the two children, it does not flip a layout property, so the gap stays inside either way. ## Properties Two sets: the box, and the label that carries it. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Pressed \| Disabled | Default | | `Active` | True \| False | False | | `Align` | Left \| Right | Left | | `Show Description` | Boolean | true | | `Label` | Text | Title of checkbox | | `Description` | Text | Insert the checkbox description here. | Two component sets, not one. Checkbox is six variants of State by Active and carries no text. _Checkbox Label is two variants of Align and holds every property a form needs: Label, Description and Show Description. The underscore marks it private, so it stays out of the Assets panel; you reach it by placing a Checkbox and switching to the label variant. The whole row is the tap target, per the label component's description. ## Tokens Every value the checkbox reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/soft-100` | #E9E9EA | #48484A | The ring when off, the whole box when disabled | | `bg/white-0` | #FFFFFF | #0E0E0F | The 24 square inside the ring, absent when disabled | | `brand/base` | #155DFC | #2B7FFF | Checked, and pressed while unchecked | | `brand/dark` | #193CB8 | #82BFFF | Pressed while checked | | `fg/white-0` | #FFFFFF | #0E0E0F | The tick, stroked at 2 | | `text/strong-950` | #0E0E0F | #FFFFFF | Label title | | `text/sub-700` | #48484A | #C7C7CC | Label description | Every one of these themes. brand/dark is the pressed fill and it goes lighter, not darker, #193CB8 to #82BFFF, because pressed has to step away from the base in whichever direction has room. The tick is fg/white-0, not the static white the name suggests, so in dark mode a checked box is a near black tick on a lighter blue. Nothing on this component is a raw primitive. ## Guidelines - Do: Put the consequence in the description. The title says what happens, the description says what it costs. - Avoid: Do not ship a checked box the user cannot uncheck. A choice with one option is not a choice, it is a sentence in the terms. Checkbox is for zero or more; for one setting that is either on or off, reach for Toggle. The line between them is when the change lands: a checkbox belongs in a form that submits, a toggle applies the moment it moves. The 44 target is already drawn into this component, so the only hit area left to add in code is the label row, which the file expects to be tappable end to end. --- --- title: Chip description: Compact controls for filtering, choosing and user added entries. The Chip in Appetite UI for Figma: states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/chip --- # Chip Chip is a compact control for filtering a list, choosing from a set, or showing an entry someone added. ## Preview A compact token you can select or remove. Filters, tags, multi-select entry. For moving between views use Tab. Specimen: Chip, Type Default, State Default, Selected False. 99 x 32, radius 12, padding 6 and 12. Click it. Selecting swaps bg/white-0 for bg/strong-950 and the label for text/white-0, and drops the border entirely. The close x is the one thing that does not switch. ## Type Five ways to lead the chip. The axis changes the element in front of the label and nothing else, no colour, no height, no radius. Specimen: Type Default, Avatar, Emoji, Flag, Brand. 16 box on Default, 20 on the other four. The bigger box costs left padding: 12 on Type Default, 6 on the four that lead with 20, so the chip measures 99 then 97. The file names Erik Holmberg at Size sm, Grinning Face, United States and the Slack mark. Brand stands in a neutral tile, not a third party logo. ## Selection One boolean axis, and the strongest thing the component does. Selected fills the whole pill instead of marking it, so a row of chips reads at a glance. Specimen: Selected False, Selected True. bg/white-0 to bg/strong-950, label to text/white-0. Both tokens invert with the theme: a selected chip is a black pill with white text in light, a white pill with black text in dark. Selected True is drawn identically in Default, Pressed and Focused, so a chip that is on carries no state reading across those three. Disabled is the exception, and it is in the next section. ## State Four drawn states on an unselected chip. Three of them are a fill change and nothing else. Disabled is the odd one: it leaves the box exactly where it was and moves the label and both icons instead. Specimen: State Default, Pressed, Focused, Disabled, then Selected True at Default and Disabled. bg/white-0, bg/weak-50, bg/soft-100, then text/disabled-400 on an unchanged box. State ships 4 options and the set holds all 40 variants: Disabled runs the full Type by Selected grid. Unselected it keeps bg/white-0 and the border and recolours the label and both icons to the disabled pair. Selected it is the one chip in the set that does not take bg/strong-950, dropping to bg/soft-100 with the same disabled label. Pressed keeps the border on Type Default and drops it on the other four; Focused pushes the left padding from 6 to 8. ## Content Two booleans, both true by default. Turn the icon off for a plain filter, turn the close button off when the chip is a label rather than something to remove. Specimen: Both true, Icon false, Close Button false, both false. 99, 83, 83, 67. The widths are arithmetic, not settings: 12 of padding, a 16 box, the label wrapper at 6 plus 31 plus 6, another 16 box, 12 of padding. Drop either box and 16 goes with it. ## Properties Three variant axes and four instance properties. 4 x 5 x 2 is 40 variants, and the set holds all 40. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Pressed \| Focused \| Disabled | Default | | `Type` | Default \| Avatar \| Emoji \| Flag \| Brand | Default | | `Selected` | False \| True | False | | `Text` | Text | Chip | | `Icon` | Boolean | true | | `Close Button` | Boolean | true | | `Icon Swap` | Instance swap, the Icons set | circle | Disabled covered three cells and now covers ten, which took the set from 30 variants to 40. It is the only state that leaves the box alone: fill, border, radius and padding all hold, and the label and both icons move to the disabled pair instead. At Selected True it is also the only state that does not take bg/strong-950. ## Tokens Every value the component reads, resolved through its aliases to the Foundations primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | The box at rest, and while disabled | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px inside stroke, unselected only | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed | | `bg/soft-100` | #E9E9EA | #48484A | Focused, and the selected disabled chip | | `bg/strong-950` | #0E0E0F | #FFFFFF | Selected, except while disabled | | `text/sub-700` | #48484A | #C7C7CC | Label, unselected | | `text/white-0` | #FFFFFF | #0E0E0F | Label, selected | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Label, disabled, selected or not | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Leading icon | | `fg/disabled-400` | #AEAEB2 | #8E8E93 | Both icons while disabled | | `colors/gray/200` | #D1D1D6 | #D1D1D6 | Avatar disc, one mode | | `colors/alpha/black/alpha-24` | #030712 at 24% | #030712 at 24% | Close x, one mode | The close x is the one problem in the set. `colors/alpha/black/alpha-24` is a primitive with a single mode, so it stays black at 24 percent wherever it lands: invisible on a selected chip in light, invisible on every chip in dark. Disabled escapes it, because there the file paints the x `fg/disabled-400`. The page reproduces both rather than swapping in a semantic token, which is a system decision. Geometry runs on `dimensions/radius/lg` 12, `dimensions/spacing/3` 12, `dimensions/spacing/1-5` 6 and `dimensions/icon/stroke` 2. ## Guidelines - Do: Use a row of chips as a filter set and let selection carry the state. Drop the close button when nothing is being removed. - Avoid: Do not move between views with chips. That is Tab, which owns the underline, the swipe and the screen it belongs to. Keep the label short enough to stay on one line. The chip hugs its text and never wraps, so a long label pushes the row into a horizontal scroll instead of breaking. --- --- title: Context Menu description: Press and hold actions for one object. The Context Menu in Appetite UI for Figma: item states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/context-menu --- # Context Menu Context Menu reveals the actions for one object on press and hold, without leaving the screen. ## Preview Actions for one specific object, raised by press and hold. Press a row to see it fill. On iOS this is .contextMenu; on Android a long press opening a DropdownMenu. Specimen: Context Menu, Slot False. 300 x 307, padding 8, gap 2, radius 32. 307 is arithmetic: 8 of padding, five 56 rows, five 2 gaps, the 1 separator, 8 again. Every row carries the same `share-2` icon because Icon Swap sits at its default on all five. Swap it per row. ## Item The building block behind every row, not for direct use. Four states, and only one paints a fill. Specimen: State Default, Pressed, Destructive, Disabled. 284 x 56, padding 6 and 16, gap 12, radius 24. Default carries no fill, so a row is invisible until pressed. Pressed takes bg/weak-50. Destructive takes state/error/base-500 on both halves and belongs last, under a separator. Disabled takes fg/disabled-400 and text/disabled-400, which resolve to the same primitive in both modes. ## Preview slot One boolean axis. Slot True raises the pressed object above the menu, which is the iOS convention and reads well on both platforms. Specimen: Slot True. 160 plus 8 plus 307 = 475. The slot ships empty, so the page marks the space rather than invent the object that would fill it. The menu below is shortened here; the slot and the gap are the file's, 160 and 8. ## Properties One axis on the published component and a slot. The rest sit on the item inside it, a building block the file marks as not for direct use. | Property | Type | Default | | --- | --- | --- | | `Slot` | False \| True | False | | `Preview` | Slot, 300 x 160 | empty | | `State` | Default \| Pressed \| Destructive \| Disabled, on the item | Default | | `Label Text` | Text, on the item | Menu item | | `Icon Swap` | Instance swap, the Icons set, on the item | share-2 | Context Menu ships 2 variants, the item ships 4. The menu composes five items and a separator, so a change to the item reaches every row of both variants. ## Tokens Every value the component reads, resolved through its aliases to the Foundations primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | The menu surface | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px inside stroke, and the separator | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed row | | `text/strong-950` | #0E0E0F | #FFFFFF | Label | | `fg/sub-700` | #48484A | #C7C7CC | Icon | | `state/error/base-500` | #FB2C36 | #FB2C36 | Destructive label and icon | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Disabled label | | `fg/disabled-400` | #AEAEB2 | #8E8E93 | Disabled icon | The shadow is the effect style `dropShadow/lg`: 0 12 18 at minus 3 on black at 8 percent, over 0 5 6 at minus 5 on black at 5 percent. Geometry runs on `dimensions/radius/3xl` 32 for the menu and `dimensions/radius/2xl` 24 for the row, with `dimensions/spacing/2` 8, `dimensions/spacing/0-5` 2, `dimensions/spacing/4` 16, `dimensions/spacing/3` 12 and `dimensions/spacing/1-5` 6. ## Guidelines - Do: Put the destructive action last, below a separator, and cap the menu at about seven rows. - Avoid: Do not open with the destructive row, and do not let this menu be the only way to reach an action. Press and hold is not discoverable, so everything here must also exist somewhere visible. The menu belongs to one object, not to the screen. Actions that apply to the whole view belong in a toolbar or an action sheet. --- --- title: Divider description: Separates content into groups with a single line. The Divider in Appetite UI for Figma: styles, tokens and when to use one at all. group: Components source: https://www.appetiteui.com/docs/divider --- # Divider Divider separates content into groups with a single line. ## Preview A visual separator, the smallest component in the system. One colour, one weight, three shapes. On iOS this is Divider; on Android HorizontalDivider and VerticalDivider. Specimen: Divider, Style Horizontal. 342 x 4, a 1px line with 2 above and below. The frame measures 4 and the line measures 1: in Figma the line is a LINE of height 0 with the stroke drawn outside it, and 2 of padding sits above and below. ## Style Three shapes on one axis. Horizontal rules off a block, With Label carries a word between two options, Vertical splits items sitting on one line. Specimen: Style Horizontal, With Label, Vertical. 342 x 4, 342 x 16, 1 x 24. With Label splits the 342 into 153, the label, 153, with 12 of gap on each side. The label is the only part that is not the line colour: it takes text/soft-500 where the line takes border/soft-100. ## In use Each style exists for one job. Seen on its own a divider is a grey line; seen in place it is the reason two things read as separate. Specimen: Horizontal between rows, With Label between actions, Vertical in a meta line. the blocks around them are stand-ins, not Button. The component's own description names a fourth style, Inset, for a list with a leading avatar. The set does not ship it, and it calls the first style Horizontal where the description calls it Full Width. ## Properties One axis and one text field. Nothing else is exposed, and nothing else needs to be. | Property | Type | Default | | --- | --- | --- | | `Style` | Horizontal \| With Label \| Vertical | Horizontal | | `Text` | Text, With Label only | or | | Style | Size | Built from | | --- | --- | --- | | Horizontal | 342 x 4 | 2 of padding, a 0 tall line, 2 of padding | | With Label | 342 x 16 | 153, gap 12, the label, gap 12, 153 | | Vertical | 1 x 24 | the same line, rotated, length 24 | The width is fixed at 342 on both horizontal styles, which is a 390 screen less 24 of side margin on each edge. Set it to fill in your own layout; nothing in the component depends on the number. ## Tokens Two values for the whole component. There is no second colour, no weight scale and no emphasis variant. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `border/soft-100` | #E9E9EA | #2F2F31 | Every line, all three styles | | `text/soft-500` | #8E8E93 | #AEAEB2 | The label on With Label | The label runs on `typography/fontSize/xs` 12, `typography/lineHeight/xs` 16 and `typography/letterSpacing/wide`.1 at the default weight. Geometry runs on `dimensions/spacing/0-5` 2, `dimensions/spacing/3` 12 and `dimensions/spacing/6` 24. ## Guidelines - Do: Use it where spacing alone stops doing the work: rows of the same weight, or two options that need a word between them. - Avoid: Do not rule off every element. A divider after each row, plus one under the last, turns a list into a table nobody asked for. A section that already sits on its own card does not need a divider above it. The card edge is the separation. --- --- title: Drawer description: A panel that slides in from the edge so filters and navigation never take the user off the screen. The Drawer in Appetite UI for Figma, states and tokens. group: Components source: https://www.appetiteui.com/docs/drawer --- # Drawer Drawer holds navigation off screen and slides it in over a scrim when you need it. ## Preview The drawer covers the leading edge of the screen and dims everything behind it. Open it from the button, dismiss it from the scrim or from the close control in its own header. Specimen: Navigation Drawer, open. 320 x 852 over a 393 wide screen, radii 0 / 28 / 28 / 0. The shell is a fixed 852, not a hug. A Spacer set to fill takes the 36 left between Recents and Account, so the account row holds the bottom edge whatever the list above it does. The phone and the Open button are stand-ins. The avatar is Avatar's Acronym type at Size lg, one initial on colors/gray/200. The account row also carries a Badge instance, which belongs on the Badge page. ## Anatomy Eight bands in one vertical frame with no gap and no padding of its own. Each band carries its own, so these numbers are the whole layout. | Band | Size | Layout | | --- | --- | --- | | `Header` | 320 x 120 | Padding 64 16 12 20. A 32 mark, a 44 close control | | `Search` | 320 x 60 | Padding 0 20 12 20 round a 280 x 48 field | | `Primary` | 320 x 144 | Gap 2, padding 0 8 8 8. Three rows | | `Divider` | 320 x 13 | Padding 0 20 12 20 over a 280 x 1 line | | `Pinned` | 320 x 176 | Gap 2, padding 0 8 8 8. A 30 label, three rows | | `Recents` | 320 x 222 | Gap 2, padding 0 8 8 8. A 30 label, four rows | | `Spacer` | 320 x 36 | Fills. It takes what the other seven leave | | `Account` | 320 x 81 | Gap 12, padding 14 16 30 20, over a 1px top rule | The eight heights add up to 852 exactly. The account rule is drawn inside the frame, so it costs no height: as a CSS border the shell would measure 853. ## The row Every line in the drawer is one component, Drawer / Item, on two axes: State says how the row is doing, Trailing says what it opens. Specimen: State, then Trailing. 304 x 44, radius 10, gap 10, padding 10 12, label 16/24. Default and Pressed take text/sub-700 at 400; Active and Accent step up to 600, one on bg/soft-100 and one on no fill. Chevron and more hold fg/soft-500 in all four states, so the trailing mark never carries the state. More is the only variant that moves geometry, dropping right padding from 12 to 8 for a 24 box. ## Properties All five sit on the row. The shell has no property and no variant: you build a drawer by stacking rows in it. | Property | Type | Default | | --- | --- | --- | | `Label` | Text | "Drawer item" | | `Icon` | Boolean | True | | `Icon Swap` | Instance swap | folder | | `Trailing` | Variant | None, Chevron, More | | `State` | Variant | Default, Pressed, Active, Accent | Label is one line and truncates rather than wraps. Icon turns the 20 leading slot off, which is how every Recents row runs. Trailing and State multiply out to the twelve variants. ## Tokens Every colour is bound to a variable and turns with the mode. The shell takes bg/white-0, the search field and Pressed row bg/weak-50, the Active row bg/soft-100, the divider and account rule border/soft-100, the mark and Accent row brand/base. | Token | Light | Dark | | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | | `bg/weak-50` | #F4F5F5 | #242427 | | `bg/soft-100` | #E9E9EA | #48484A | | `border/soft-100` | #E9E9EA | #2F2F31 | | `brand/base` | #155DFC | #2B7FFF | | `text/strong-950, fg/strong-950` | #0E0E0F | #FFFFFF | | `text/sub-700, fg/sub-700` | #48484A | #C7C7CC | | `text/soft-500, fg/soft-500` | #8E8E93 | #AEAEB2 | The scrim is bg/scrim, black at 40 percent in light and 48 in dark. fg and text hold the same value at every step, but icons bind to fg and words to text, so a later split reaches both. Search field radius 18 is dimensions/radius/xl, the close control is dimensions/radius/full, and the shell 28 and row 10 are typed on the frame. ## Guidelines A drawer is a list of destinations with one thing happening in it. Two emphasis states, used once each, keep it readable. - Do: One Accent row for the action that starts something, one Active row for the conversation on screen. - Avoid: Accent on more than one row. Emphasis spread across the list is emphasis spent, and the row that acts stops standing out. Pick the trailing mark for the gesture: chevron on a row that opens another list, more on a row with its own menu, nothing on a row that simply goes somewhere. Recents rows run with Icon off, so the conversations read as text. --- --- title: Dropdown description: A short list of options anchored to the control that opened it. The Dropdown in Appetite UI for Figma: states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/dropdown --- # Dropdown Dropdown opens a short list of options, anchored to the control that opened it. ## Preview A menu anchored to a trigger. Press the label to open it. Past about six options on a phone, use Action Sheet or Picker instead. Specimen: DropdownMenu, State Close then Open. trigger 44 tall, menu 300 x 296. Closed, the trigger has no fill at all. Open, it takes bg/soft-100 and the caret flips from down to up. 296 is arithmetic: 12 of padding, the 32 title, four 56 rows, four 4 gaps, 12 again. The label does not move while that happens: in the file the open menu is positioned absolutely inside the component, so the trigger measures 106 by 44 either way, and here the wrapper is held at the menu's own 300 so the label keeps its left edge. ## Option The building block behind every row, not for direct use. Four states, three of them just a fill. Checked adds a brand tick on the right. Specimen: State Default, Active, Pressed, Checked. 276 x 56, radius 18, padding 6 and 16, gap 12. Pressed is darker than Active here, the reverse of Context Menu, where Pressed takes bg/weak-50. The title and description sit at gap 0, so 24 over 20 fills the 44 the row leaves after its padding. ## Only Popover The second axis drops the trigger and ships the menu alone, for anchoring to something the component does not own: an icon button, a row, a long press. Specimen: Only Popover True. 300 x 296, no trigger above it. Only three of the four axis pairs exist. A closed popover would be nothing on screen, so the file does not draw it. ## Properties Two axes and a boolean on the published component, four more on the option inside it. | Property | Type | Default | | --- | --- | --- | | `State` | Open \| Close | Close | | `Only Popover` | False \| True | False | | `Title` | Boolean | true | | `Title Text` | Text | Dropdown title | | `State` | Default \| Active \| Pressed \| Checked, on the option | Default | | `Icon, Description` | Booleans, on the option | true | | `Icon Swap` | Instance swap, on the option | circle | DropdownMenu ships 3 variants, not 4: a closed popover would be nothing on screen. The option ships 4 and is composed four times into each open menu. ## Tokens Every value the component reads, resolved through its aliases to the Foundations primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Menu surface, and the option at rest | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px inside stroke on the menu | | `bg/soft-100` | #E9E9EA | #48484A | Open trigger, and the pressed option | | `bg/weak-50` | #F4F5F5 | #242427 | Active option | | `text/sub-700` | #48484A | #C7C7CC | Trigger label | | `text/strong-950` | #0E0E0F | #FFFFFF | Option title | | `text/soft-500` | #8E8E93 | #AEAEB2 | Menu title and option description | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Caret and option icon | | `brand/base` | #155DFC | #2B7FFF | The tick on Checked | The shadow is the effect style `dropShadow/md`: 0 10 15 at minus 3 on black at 5 percent, over 0 4 6 at minus 4 on black at 8 percent. Geometry runs on `dimensions/radius/lg` 12 for the trigger, `dimensions/radius/2xl` 24 for the menu and `dimensions/radius/xl` 18 for the option, with the trigger height on `dimensions/spacing/11` 44. ## Guidelines - Do: Keep it to a handful of options and let the title say what the list is for. Checked marks the current choice, so the menu answers its own question. - Avoid: Do not use it for a long list. Past about six options on a phone the component's own description sends you to Action Sheet or Picker. The menu is anchored to its trigger, so it opens where the finger already is. If the thing being chosen has no trigger on screen, that is Only Popover, not a dropdown with an invisible button. --- --- title: File Upload description: Add a file, then see progress and errors. The File Upload in Appetite UI for Figma: 6 states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/file-upload --- # File Upload File Upload lets people attach a file, and reports progress, success and failure along the way. ## Preview The surface that takes a file. Default invites the first tap. Bind State to the real request, never decorative: on iOS fileImporter, on Android GetContent. Specimen: File Upload, tap to run the cycle. 340 x 180, radius 32, padding 24, 1px dash 5 on 3. Tap it. Default to Active to Uploading to Upload, the four states in order. Tap again to restart. Inside the box: a 24 icon, a 292 text column and, while uploading, a 292 by 4 track, stacked 12 apart with 4 between the two lines. Height is fixed at 180, so nothing moves as the state changes. ## State Six states, one 340 by 180 box. What changes: the fill, the edge, the icon and which of the two lines carries the message. Specimen: Default, Active, Uploading, Upload, Error, Disabled. one box, six skins. In order: Default, white with a grey dash. Active, brand/lighter with a brand edge, press and drag-over. Uploading adds the track, filled to 191 of 292. Upload swaps the icon for the file type mark, filename on the primary line. Error recolours the edge, icon and supporting line, which carries the reason. Disabled has no edge. The four strings never change, only their colour. The dash is 5 on 3, an SVG rect: a CSS dashed border cannot set its pattern and Chrome draws roughly 3 on 3. The Upload mark is Adobe Acrobat in the file, a neutral tile here. ## Properties One axis and two text fields. No size, no layout, no boolean: the box is the same on every state. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Active \| Uploading \| Upload \| Error \| Disabled | Default | | `Label` | Text | Tap to upload a file | | `Supporting Text` | Text | PNG, JPG, PDF · Up to 10 MB | | State | Fill | Edge | Leading and labels | | --- | --- | --- | --- | | Default | bg/white-0 | border/sub-200 | upload icon, primary in information blue | | Active | brand/lighter | brand/base | the same, on a blue ground | | Uploading | bg/white-0 | border/sub-200 | primary turns strong, a track appears | | Upload | bg/white-0 | border/sub-200 | the file type mark, primary is the filename | | Error | state/error/lighter-50 | state/error/base-500 | circle-alert, supporting carries the message | | Disabled | bg/soft-100 | none | everything on the disabled token | Disabled is the only state without an edge. It reads as a surface, not a target. Error is the only one that recolours the supporting line. ## Tokens Every value the component reads, resolved through its aliases to the Foundations primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Default, Uploading, Upload | | `border/sub-200` | #D1D1D6 | #48484A | The dashed edge at rest | | `state/information/base-500` | #155DFC | #155DFC | Upload icon and the inviting label, one mode | | `brand/base` | #155DFC | #2B7FFF | Active edge and the progress fill | | `brand/lighter` | #EFF6FF | #132150 | Active fill | | `state/error/base-500` | #FB2C36 | #FB2C36 | Error edge, icon and message | | `state/error/lighter-50` | #FEF2F2 | #FB2C36 at 16% | Error fill, an alpha in dark | | `bg/soft-100` | #E9E9EA | #48484A | Disabled fill and the progress track | | `text/strong-950` | #0E0E0F | #FFFFFF | Primary label once there is a file | | `text/sub-700` | #48484A | #C7C7CC | Supporting line | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Everything on Disabled, with fg/disabled-400 | `state/information/base-500` aliases `colors/blue/500` in both modes, so it does not lift in dark the way `brand/base` does. A dark Active card carries a #2B7FFF edge around a #155DFC label. The label is the half you have to read. Geometry runs on `dimensions/radius/3xl` 32, `dimensions/spacing/6` 24, `dimensions/spacing/3` 12 and `dimensions/border/1`. ## Guidelines - Do: Put the reason on the supporting line. The state has a place for the message, so use it instead of a toast that leaves. - Avoid: Do not leave the supporting line empty. What you accept and how big it can be is the one thing the user needs before they choose. Uploading, Upload and Error are answers from the server. Wire State to the real request: a card that says Uploading while nothing is in flight is worse than no state at all. --- --- title: Input description: Collects text, numbers and passwords with a label, a hint and an error. The Input in Appetite UI for Figma: 5 states and the tokens behind them. group: Components source: https://www.appetiteui.com/docs/input --- # Input Input collects one line of text, a number or a password, with a label, a hint and an error. ## Preview Text Input, type Default, state Placeholder. The field is underlined, not boxed: no fill, no radius, one rule on the bottom edge. Type in it. Specimen: Type Default, State Placeholder. 340 x 104, label 20, field 56, hint 16, gap 6. ## Type Three types. Default is a plain field. Left and Right Selection put a picker on one side of a 32px divider, for a dialling code or a currency. Specimen: Type. Default, Left Selection, Right Selection. ## State Five states. Only the rule and the text move, so the box never changes height and a form does not jump on error. Focused adds a clear button before the trailing icon. Specimen: State. Placeholder, Filled, Focused, Error, Disabled. ## Chip Input The same underlined field with a tag icon, for entries that become tokens. Padding drops to 8 so a row of chips sits on the rule rather than above it. Specimen: Chip Input. field padding 8, gap 8, tag icon 20. ## Counter Input The one input in the set that is not underlined. A pill with a full border and two 32px buttons, for a quantity you nudge rather than type. The buttons work. Specimen: Counter Input. radius 9999, padding 6 and 16, buttons 32, value 18/28 Semi Bold. ## Digit Input One cell per digit, for a code that arrives by SMS. 80px cells at radius 18, set at 24/32 Semi Bold so the number reads at arm's length. Specimen: Digit Input. 80 x 80, radius 18, padding 16 and 8, 24/32 Semi Bold. ## Text Area The same underlined field, four lines deep, with a counter in the hint row. The box is fixed: a long answer scrolls inside it instead of pushing the rest of the form down the screen. Type in it. Specimen: State Placeholder. 340 x 168, field 120, padding 12 and 12, value 16/24, gap 6. Five states, one set. Only the rule and the text colour move, so the box never changes height and a form does not jump when a field goes red. ## Stepper Two buttons and a number, for a value you change by one. No field and no label: the stepper sits in a row that already says what it counts. The buttons work. Specimen: Size md, State Default. sm 106 x 36, md 122 x 44, gap 12, value 16/24 Semi Bold. Six variants: two sizes by three states. Min and Max are not a disabled stepper. Only the button that would cross the bound goes flat, so the other one still reads as live. ## Properties Six sets share one Figma page. This table is the Text Input set, then the properties the other five add. Chip, Counter and Digit drop the ones they have no use for. | Property | Type | Default | | --- | --- | --- | | `Type` | Default \| Left Selection \| Right Selection | Default | | `State` | Placeholder \| Focused \| Filled \| Error \| Disabled | Placeholder | | `Input Label`, `Help`, `Icon Left`, `Icon Right` | Boolean | true | | `Optional`, `Tooltip` | Boolean | true | | `Icon Left Swap`, `Icon Right Swap` | Instance swap | phone, chevron-down | | `Input Label Text`, `Input Text`, `Help Text` | Text | Title of input, Placeholder text, hint | | `Counter`, `Counter Text` Text Area | Boolean, Text | true, 0/500 | | `Size` Stepper | sm \| md | sm | | `State` Stepper | Default \| Min \| Max | Default | | `Value` Stepper | Text | 2 | Text Input 15 variants, Text Area 5, Chip Input 5, Counter Input 5, Digit Input 5, Stepper 6. Left Selection ships the United States flag beside a +1 dial code. It read +358, which is Finland, until the code was corrected in the Figma file on 7 Sep 2026 to match the flag. ## Tokens Every value the input reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `border/sub-200` | #D1D1D6 | #48484A | The rule under the field, and the divider | | `brand/base` | #155DFC | #2B7FFF | The rule while focused | | `state/error/base-500` | #FB2C36 | #FB2C36 | The rule and the hint on error | | `text/soft-500` | #8E8E93 | #AEAEB2 | Label title, placeholder and every icon | | `text/strong-950` | #0E0E0F | #FFFFFF | The typed value, and the Counter label | | `text/sub-700` | #48484A | #C7C7CC | Hint text | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Value, hint and icons when disabled, and (Optional) | | `border/soft-100` | #E9E9EA | #2F2F31 | Counter pill, counter buttons and Digit cell | | `bg/white-0` | #FFFFFF | #0E0E0F | Counter and Digit fill | | `bg/soft-100` | #E9E9EA | #48484A | The stepper button at its bound | | `fg/sub-700` | #48484A | #C7C7CC | The stepper glyphs | | `fg/disabled-400` | #AEAEB2 | #8E8E93 | The stepper glyph at its bound | ## Guidelines - Do: Keep the hint slot filled from the start and swap its text on error. The field never changes height, so the form does not jump. - Avoid: Do not signal the error with the red rule alone. Colour is not a message, and the rule is the only thing a colour blind user cannot read. --- --- title: List description: Rows of related items with a leading visual, text and an action. The List in Appetite UI for Figma: states, swipe actions and tokens. group: Components source: https://www.appetiteui.com/docs/list --- # List List presents rows of related items, each with a leading visual, text, and an action. ## Preview One row of a scrollable list. The trailing is the promise: a chevron drills in, a toggle flips something now, a radio picks one of several, a value label only reports. Choose it before the label. Specimen: List, Trailing Chevron, State Default. 340 x 76, radius 24, padding 16, gap 12. The width is fixed at 340 and the height hugs. 44 of content plus 16 of padding top and bottom is the 76; the 44 is 24 over 20 with no gap between the two lines. Keep the target at 44pt on iOS and 48dp on Android; this row clears both. ## Trailing Four affordances on one axis. Two are 20 glyphs, two are 44 targets. The row stays 76 tall either way, the content column already measures 44. Specimen: Chevron, Text, Toggle, Radio. 20 glyph, 14/20 label, 44 x 28 track, 28 disc. Chevron and Text sit on grey, Toggle and Radio on brand/base. Both controls ship switched on in all twelve variants, so the file has no off state: drive them from your own data, not from this row. Text reports a value chosen somewhere else and never takes a tap. ## State Three states across all four trailings, twelve variants in all. Only the fill and the ink move. Nothing resizes or shifts. Specimen: Default, Pressed, Disabled. bg/white-0, bg/weak-50, bg/white-0 again. Pressed is the only state that touches the row's fill and leaves the toggle alone: a press on the row is not a press on the control. Disabled goes back to bg/white-0 and moves all ink to the disabled token, but the leading tile keeps its faded fill, so the row still reads as a row. ## Swipe actions A private set behind the row. The row does not resize, it slides by 76 per action and the square tiles wait underneath. One to three per side; past three the tiles are too narrow to hit. Specimen: Side Trailing then Leading, Actions 3. tiles 76 x 76, no radius, glyph 20 over a 12/16 label at gap 4. Trailing puts the destructive one furthest from the thumb: Archive, Flag, Delete. Leading carries the positive ones, Pin, Read, Archive. Every action must also exist somewhere the user can find it; a swipe teaches nobody it is there. Archive sits on bg/surface-800, which inverts in dark to #D1D1D6 while its label stays on static white. ## Properties Two axes and five properties, twelve variants. | Property | Type | Default | | --- | --- | --- | | `Trailing` | Chevron \| Text \| Toggle \| Radio | Chevron | | `State` | Default \| Pressed \| Disabled | Default | | `Leading Icon`, `Description` | Booleans | true, true | | `Icon Swap` | Instance swap | bell | | `Label`, `Supporting text` | Text | List item, Description | The swipe set has its own axes, `Side` and `Actions`, six variants. Description off leaves one 24 line in the column; Leading Icon off hands its 40 and the 12 gap back. The row hugs, so the trailing sets the floor. ## Tokens Every value the row reads, resolved to its primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Row at rest, handle, dot | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed row | | `state/faded/lighter-50` | #F4F5F5 | #6A7282 at 16% | Leading tile | | `state/faded/base-500` | #8E8E93 | #636366 | Leading glyph | | `text/strong-950` | #0E0E0F | #FFFFFF | Primary label | | `text/sub-700` | #48484A | #C7C7CC | Supporting and value label | | `text/soft-500` | #8E8E93 | #AEAEB2 | Chevron | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Disabled ink, with fg/disabled-400 | | `brand/base` | #155DFC | #2B7FFF | Track and disc | | Swipe token | Light | Dark | Applied to | | --- | --- | --- | --- | | `state/error/base-500` | #FB2C36 | #FB2C36 | Delete | | `state/warning/base-500` | #FF6900 | #F54900 | Flag | | `state/success/base-500` | #22C55E | #16A34A | Read | | `state/information/base-500` | #155DFC | #155DFC | Pin, one value both modes | | `bg/surface-800` | #2F2F31 | #D1D1D6 | Archive, inverts | Disabled drops both controls to `bg/soft-100`, #E9E9EA and #48484A. Tile labels take `text/on-color/strong`, tile glyphs `icon/on-color`, both static white either mode. Two greys do one job and part company in dark: the leading glyph is `state/faded/base-500`, which steps down to `colors/gray/600`, while the chevron on `text/soft-500` lifts to #AEAEB2. Both bind `dimensions/icon/stroke` 2, yet the chevron renders 2.5. Geometry: `radius/2xl` 24, `radius/lg` 12, `spacing/4` 16, `spacing/3` 12. ## Guidelines - Do: Match the trailing to what the tap does. A chevron promises a screen, so the supporting line can summarise it. - Avoid: Do not put a toggle on a row that also opens a screen. Two targets, and the bigger one wins the thumb. Keep the label to one line. The content column is 224 with the leading icon on, about thirty characters at 16 Semi Bold, and a row that wraps is no longer 76. --- --- title: Modal description: Interrupts the screen with a decision that has to be made. The Modal in Appetite UI for Figma: dialog, sheet, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/modal --- # Modal Modal interrupts the screen with a decision that has to be made before anything else continues. ## Preview A dialog that interrupts and asks for a decision. One primary action, one secondary, a close. If the answer can wait, or there is no decision, this is the wrong component. Specimen: Modal, Title and Slot. 340 x 344, radius 32 32 0 0, padding 24 20 20. Three parts at gap 16: a 32 title row, the 140 slot, a 96 button column. Height hugs inside 24 of top padding and 20 at the bottom. The stroke sits outside and costs no size. The title fills the row, the 32 close takes what it needs. Radius is 32 on the top two corners, 0 on the bottom two, on a component the file calls a centred dialog. ## Slot The middle block is a real Figma slot, a hole with no fill and no content of its own. Everything above and below is fixed; the slot is the only thing a decision changes. Specimen: Slot filled with copy. 300 x 140, buttons 44 at gap 8. The pink marks an empty slot in the docs, not something the component ships. The buttons are a Brand Filled and a Brand Link at md, stacked, not side by side, so a long label never truncates. ## Modal Sheet A separate component for the other kind of interruption: the bottom sheet, for secondary flows and pickers. The default overlay on both platforms, a sheet with detents on iOS, a ModalBottomSheet on Android. Specimen: Modal Sheet, no properties. 340 x 34, back sheet 260 x 11, front sheet 340 x 23. The hatch is this page's ground. The sheet is white on glass, so a white stage leaves nothing to see and nothing for the blur to act on. Only the top edge ships: 11 of a second sheet behind, then 23 of the front one. The Drag Handle sits at y 34 inside a 34 tall frame that clips, so it never draws. liquidGlass/mid uses refraction and dispersion CSS cannot express, so this runs a 72 percent fill (55 in dark) with a 15px blur, an approximation, not a match. ## Properties Neither component is a variant set. Modal has two properties, Modal Sheet has none. | Property | Type | Default | | --- | --- | --- | | `Title` | Text, on Modal | Title of Modal | | `Slot` | Slot, on Modal | empty, 300 x 140 | No State axis, no size axis, no boolean. The two buttons are Button instances with their own properties: a destructive dialog swaps the first to Type Error on the instance, not here. The close is a Compact Button at Style Transparent. A modal with no decision is a Toast or an Alert. ## Tokens Every value the two components read, resolved to their primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Modal surface, sheet front | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px outside stroke on the modal | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Close glyph | | `brand/base` | #155DFC | #2B7FFF | Filled button, link label | | `static/static-white-0` | #FFFFFF | #FFFFFF | Filled button label | | `bg/glass` | #FFFFFF at 72% | #242426 at 55% | Sheet ground | | `alpha/white/alpha-24` | #FFFFFF at 24% | #FFFFFF at 24% | Back sheet, one value both modes | | `alpha/black/alpha-16` | #030712 at 16% | #030712 at 16% | Drag handle, one value both modes | The last two are single mode primitives: the back sheet stays white 24 percent and the drag handle black 16 percent even on a dark sheet, though the handle never renders. Geometry runs on `radius/3xl` 32, `spacing/6` 24, `spacing/5` 20, `spacing/4` 16 and `spacing/2` 8, `icon stroke` 2 and 2.5 on the close. ## Guidelines - Do: Put the decision in the title and the verb on the button. Discard and Keep editing say what happens; OK and Cancel make the user re-read the title. - Avoid: Do not ask a question the buttons cannot answer. Sure about what, and what does OK agree to? One modal at a time, never one opened from another. The close button and the secondary action do the same job here, so give the secondary a real label, not a second dismiss. Reach for Modal Sheet when the content is a list or a picker: a sheet is easier to reach with a thumb and easier to leave. --- --- title: Navigation description: Moves people between the main sections of an app. The Navigation bar in Appetite UI for Figma: 2 to 5 tabs, badges, states and tokens. group: Components source: https://www.appetiteui.com/docs/navigation --- # Navigation Navigation is the bottom bar that moves people between the main sections of an app. ## Preview The bottom tab bar, and the only persistent navigation in the app. Two to five destinations, always the same set, always in the same order. If a destination comes and goes, it does not belong here. The specimen is live, so pick one. Specimen: Navigation, Tabs 2. 340 x 62, pill, padding 2, inner row 336 x 58, pill 168. A pill, not a bar across the screen bottom. The 1px stroke is drawn outside, so it costs no size, and under it sits dropShadow/sm, two shadows that lift the pill off whatever it floats over. The icon is 24, the label 12/16 Semi Bold, and the two measure 44 with their gap, centred in the 58. The active pill travels rather than jumps: motion/toggle/state, duration/fast 120 with easing/standard. The bar itself carries no motion in the file, so that preset stands in, since it is the file's own answer for a control changing state. ## Tabs One axis, four counts, and nothing changes but the arithmetic. The bar stays 340 by 62 and each destination takes an equal share of the 336 inside it. Specimen: Tabs 2, 3, 4 and 5. 168, 112, 84 and 67.2 per destination. Five is a real ceiling: 67.2 leaves about eight characters at 12 Semi Bold before the label truncates. At two, a Segmented Control is usually the better answer, since a bar that only ever holds two destinations spends the whole bottom of the screen on one choice. ## Active and badge The label behind every destination is a private component with one axis and three properties. Active is a fill and two colours. Badge is a dot. Specimen: Active False, Active True, Badge on both. pill on alpha-10, brand icon and label, 6 dot. The dot sits flush with the icon's top right corner rather than hanging off it, so it never widens the destination. It carries no count. This is the Badge component at Type Dot: something is waiting, nothing about how much. The label component ships with Badge true, and all four published bar variants set it false. ## Properties One axis on the published bar. Everything else lives on the private label inside it. | Property | Type | Default | | --- | --- | --- | | `Tabs` | 2 \| 3 \| 4 \| 5 | 2 | | `Active` | False \| True, on the label | False | | `Label` | Text, on the label | Title | | `Icon Swap` | Instance swap, on the label | circle | | `Badge` | Boolean, on the label | true | Four variants on the bar, two on the label. Set Active on exactly one destination: the component will happily light two, and nothing in the file stops you. The bar has no Disabled and no Pressed, so a destination that is unavailable should not be in the bar at all. ## Tokens Every value the bar and its labels read, resolved to their primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Bar surface | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px outside stroke | | `brand/base` | #155DFC | #2B7FFF | Active icon and label | | `text/soft-500` | #8E8E93 | #AEAEB2 | Inactive label | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Inactive icon | | `state/error/base-500` | #FB2C36 | #FB2C36 | Badge dot | | `alpha/gray/alpha-10` | #6A7282 at 10% | #6A7282 at 10% | Active pill, one value both modes | The active pill is a Foundations primitive with a single mode, so it stays #6A7282 at 10 percent in dark, a faint light wash on a #0E0E0F bar rather than a value chosen for that ground. The icon and label carry the state as well, so the pill is not the only signal, but it is the one that reads first. Geometry runs on `radius/full`, `spacing/2` 8 and `spacing/1` 4, with the shadows on the effect styles `dropShadow/sm` and `dropShadow/xs`. ## Guidelines - Do: Name the place, not the action. A tab bar switches destinations, so Home, Search and Inbox are nouns a user can point at. - Avoid: Do not put a verb in the bar, and do not write a label the width cannot hold. Create new document is an action, and it truncates. Keep the set fixed. A tab that appears only sometimes teaches the user that the bottom of the screen cannot be trusted, and the whole value of a persistent bar is that the same thing is always in the same place. If the destination count changes with the account type, pick the larger set and disable nothing: build the smaller product a smaller bar. --- --- title: Pagination description: Shows how many pages there are and where the reader is. Pagination in Appetite UI for Figma: dots, numbers, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/pagination --- # Pagination Pagination shows how many pages or slides there are, and which one someone is on. ## Preview Where you are in a paged sequence. Dots for carousels and onboarding, Numbered for lists and tables. The component's own advice: prefer scrolling on mobile wherever the content allows. Specimen: Pagination, Style Numbered, Active Center. 356 x 36, nine 36 targets at gap 4. Every width here is arithmetic: nine 36 targets and eight 4 gaps make 356, seven targets make 276. 356 does not fit a 390 screen once you add a page margin. ## Dots Three dots, 26 by 24. The only thing that moves is which one is big. For a carousel or onboarding, where the user swipes and the dots report. Specimen: Active First, Center, Last. current 8 on brand/base, the others 5, gap 4. Three is what the component ships, not a limit of the idea: past about six the dots stop being countable. The current dot is 8 and the others 5, so size carries the state as well as colour, readable without telling blue from grey. The inactive dots are single mode, so they do not lift in dark. ## Numbered Two arrows and a run of numbers with the middle truncated. The three variants are three positions in the sequence, each truncating differently. Specimen: Active First, Center, Last. 276, 356 and 276, arrows disabled at the ends. First disables the back arrow, Last the forward one, Center enables both and pays with two ellipses. The ellipsis is the Disabled state of the same 36 target, so it looks pressable and is not: give it no tap handler. The numbers are the file's placeholders, not a rule. ## Page states The private target behind every number carries four states: two circles you can see, one you cannot, and the ellipsis. Specimen: Default, Pressed, Active, Disabled. 36 x 36, 14/20 at ls .1, radius 18 or full. Default has no fill, Pressed takes `bg/weak-50`, Active takes `brand/base` and turns the label Semi Bold, Disabled is the ellipsis. Active is the only state that changes the weight. The chevrons are their own component with one boolean and no pressed state. ## Properties Two axes on the published component, six variants. The three private blocks behind it carry their own. | Property | Type | Default | | --- | --- | --- | | `Style` | Dots \| Numbered | Dots | | `Active` | First \| Center \| Last | First | | `State` | Active \| Default \| Pressed \| Disabled, on _Page | Active | | `Disabled` | False \| True, on _Chevron | True | | `Active`, `Background` | True \| False, Dark \| Light, on _Dot | False, Dark | No text properties anywhere: the numbers are baked into each variant, so real page numbers mean overriding the text on each instance. The widths do not change; a 36 target holds two digits comfortably and three at a squeeze. ## Tokens Every value the component reads, resolved to its primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | Active page, active dot | | `bg/white-0` | #FFFFFF | #0E0E0F | Active page label | | `bg/weak-50` | #F4F5F5 | #242427 | Pressed page | | `text/strong-950` | #0E0E0F | #FFFFFF | Every other page number | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Ellipsis | | `fg/sub-700` | #48484A | #C7C7CC | Enabled chevron | | `fg/disabled-400` | #AEAEB2 | #8E8E93 | Disabled chevron | | `alpha/gray/alpha-16` | #6A7282 at 16% | #6A7282 at 16% | Inactive dot, one value both modes | The published dots override what the `_Dot` main draws, and both values are single mode Foundations primitives, so neither themes. The bar also mixes the axis: its active dot is the Dark variant and its inactive ones are Light. ## Guidelines - Do: Use Dots where the user swipes and the indicator only reports. Nothing to press, nothing to read, and it survives any width. - Avoid: Do not put nine 36 targets across a phone. On mobile the honest default is not to paginate at all. Infinite scroll or a Load more button beats a row of small round targets. Reach for Numbered when the user needs to jump, not advance. If they only ever go forward, one arrow and a count is the whole control. --- --- title: Picker description: Choose a date or a range from a calendar. The Date Picker in Appetite UI for Figma: day states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/picker --- # Picker Date Picker lets people choose a date, or a range of dates, from a calendar. ## Preview A calendar grid for one date. Use it when the shape of the month matters and a scrolling wheel would hide it. iOS: DatePicker, graphical style. Android: DatePicker. Specimen: Date Picker, no properties. 340 x 400, radius 32, padding 20, dropShadow/2xl. Every number is the file's own: January 2026, the 16th selected, the 2nd, 12th, 17th and a spilled 3rd marked. Three parts at gap 16. Seven 36 cells and six 8 gaps fill exactly the 300 the padding leaves, so the card cannot shrink and the stage scrolls below 340. Monday takes one letter, the other six take two. ## Date selector The month header is its own private component, not part of the grid, and the only control in the card that changes what the grid shows. Specimen: Date Selector. 300 x 44, padding 6, gap 8, radius full. A bg/weak-50 pill holding two 32 Compact Buttons at Style White, with a 14/20 Semi Bold month label filling the middle. The carets are filled vectors, not Lucide strokes, so they ignore the icon stroke variable. The main component sets radius 18 on a 44 pill; the instance in the picker overrides it to full, and that is what ships. ## Day cell The private target behind every number. Four states crossed with one boolean, eight variants, all of them a 36 circle. Specimen: Default, Pressed, Active, Disabled. 36 circle, 14/20 Semi Bold at ls .1. Specimen: The same four with Marked. 3 dot, centred, 6 up from the bottom. Default has no fill, number on text/sub-700. Pressed takes bg/weak-50 and lifts it to text/strong-950. Active is brand/base under a static white number. Disabled is a spilled day from the next month. The dot is absolutely placed, so it never moves the number, and it recolours over Active and Disabled. In the second row, Pressed with Marked drops the number back to text/sub-700. ## Properties The published component has none at all. Every knob lives on a private block inside it. | Property | Type | Default | | --- | --- | --- | | `Date Picker` | No properties | 340 x 400 | | `State` | Default \| Active \| Disabled \| Pressed, on _Day Cell | Default | | `Marked` | False \| True, on _Day Cell | False | | `Date` | Text, on _Day Cell | 1 | | `Text` | Text, on _Day Label | M | | `Edit Date` | Text, on _Date Selector | January 2026 | | Building block | Variants | Size | | --- | --- | --- | | `_Day Cell` | 8, on State and Marked | 36 x 36 | | `_Day Label` | 1 | 36 x 36 | | `_Date Selector` | not a set | 300 x 44 | Both cell and label carry a `Type` axis with exactly one option, so it is an axis that cannot vary. And the picker's own description sends you to the date selector for a compact inline choice, but that selector is `_Date Selector`, a private block marked not for direct use. Read the advice as a plan, not as something you can act on today. ## Tokens Every value the picker reads, resolved to its primitive. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | Card, caret buttons, Dismiss | | `border/soft-100` | #E9E9EA | #2F2F31 | Card edge and the Dismiss outline | | `bg/weak-50` | #F4F5F5 | #242427 | Selector pill and the pressed cell | | `text/sub-700` | #48484A | #C7C7CC | Month label, day numbers, Dismiss | | `text/soft-500` | #8E8E93 | #AEAEB2 | Day labels | | `text/strong-950` | #0E0E0F | #FFFFFF | Pressed cell number | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Spilled days, with fg/disabled-400 on their dot | | `fg/sub-700` | #48484A | #C7C7CC | Carets | | `brand/base` | #155DFC | #2B7FFF | Selected cell, marked dot, Submit | | `brand/lighter` | #EFF6FF | #132150 | The dot inside a selected cell | | `static/static-white-0` | #FFFFFF | #FFFFFF | Selected cell number, Submit label | In dark the selected cell is #2B7FFF under a static white number and its dot goes to #132150, a very dark blue on that blue. Legible, but only just, and the one pairing on this component worth checking on a real screen. Geometry runs on `radius/3xl` 32, `radius/full`, `spacing/5` 20, `spacing/4` 16 and `spacing/2` 8, with the card on `dropShadow/2xl`. ## Guidelines - Do: Let the dot mean one thing and say what it is. Something happens on that day, and the selected cell is the only thing carrying a fill. - Avoid: Do not fill a run of cells to fake a range. This component chooses one date, and three filled circles read as three selections, not as one span. Use the grid when the shape of the month is part of the decision: a weekend, a deadline near the end, a run of marked days. When the answer is just a date and the month is irrelevant, a wheel or a text field is faster and far smaller. There is no range variant here, no time and no year jump, so a booking flow that needs two dates needs two pickers or a different component. --- --- title: Progress description: A spinner for an unknown wait, a bar for a known one. Progress in Appetite UI for Figma: 3 variants, sizes, values and tokens. group: Components source: https://www.appetiteui.com/docs/progress --- # Progress Progress shows that work is happening: a spinner when the wait has no end in sight, a bar when it does. ## Preview Two ways to say the app is busy. Spinner when you cannot tell how long, bar when you can. On iOS both are ProgressView, on Android CircularProgressIndicator and LinearProgressIndicator. Specimen: Spinner base 24, Progress Bar at 50. both on brand/base over bg/soft-100. The file says 8 steps at 80ms, so a full turn takes 640ms. Here that is one arc on a steps(8) animation, not eight frames swapped by hand. Reduced motion stops it. ## Spinner Four sizes crossed with eight frames, 32 variants. The arc is identical in all of them, only the box changes. Specimen: sm 16, md 20, base 24, lg 32. ring is 28 percent of the radius: 2.24, 2.8, 3.36, 4.48. Specimen: Frame 1 to 8, held still. 45 degrees apart, eight of them close the circle. Every frame is two ellipses at the full box, a track and a 270 degree indicator, both at an inner radius of 0.72. The ring scales with the box. The frames exist so the animation can live in Figma. In code you rotate one arc. ## Progress Bar 320 by 8, clipping, with an indicator inside it. Five variants set the width, nothing else changes. Specimen: Value 0, 25, 50, 75, 100. indicator widths 0.01, 80, 160, 240, 320. Value 0 is not an empty track. The indicator is a real node 0.01 wide, so it rounds to nothing but is still there. Value is a five step axis, not a number, so real progress means overriding the width on the instance rather than picking a variant. ## Properties Two separate sets, three axes between them. No text, no icons, no slots. | Property | Type | Default | | --- | --- | --- | | `Size` | sm \| md \| base \| lg, on Spinner | sm | | `Frame` | 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8, on Spinner | 1 | | `Value` | 0 \| 25 \| 50 \| 75 \| 100, on Progress Bar | 0 | | Set | Variants | Size | | --- | --- | --- | | `Spinner` | 32, Size crossed with Frame | 16, 20, 24, 32 square | | `Progress Bar` | 5, on Value | 320 x 8 | Frame is a timeline, not a state. It exists so the spin can be assembled in Figma. Never expose it: pick a Size, rotate one arc for 640ms, loop. Five stops of Value are enough to design against, never enough to ship. Neither set carries a label, a buffer track or an indeterminate bar. ## Tokens Two colours across both sets. That is the entire palette. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/soft-100` | #E9E9EA | #48484A | Spinner track ring, bar track | | `brand/base` | #155DFC | #2B7FFF | Spinner arc, bar indicator | In light the track resolves to `colors/gray/100`, in dark to `colors/gray/700`, so it lifts off the background in both modes. The brand goes one step lighter in dark. There is no success, warning or error progress here. ## Guidelines - Do: Let the spinner promise nothing but that something is happening. Sign in, a network call, a sync with no total: that is exactly the wait it was drawn for. - Avoid: Never put a percentage next to the spinner. If you can count it, the bar shows it properly. If you cannot, the number is a guess and people will hold you to it. One question: do you know the total. Uploads, downloads and multi step forms do, so they get the bar bound to the real number. Everything else gets the spinner. Use the smallest size the surface allows, one on screen at a time. Past a few seconds, say what is taking so long. --- --- title: Progress Steps description: Shows how many steps a flow has and how far through it someone is. Progress Steps in Appetite UI for Figma: states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/progress-steps --- # Progress Steps Progress Steps shows how many steps a flow has, and how far through it someone is. ## Preview Where you are in a sequence with a known end. Checkout, onboarding, a form in parts. Done steps carry a check, the current one carries its number, the rest wait. Specimen: Steps 5, Current 3. 272 x 24, markers 24, connector 2. Three kinds of marker in one row: done, current, upcoming. The row is 272 wide in all twelve variants, so the step count never changes the space it takes. ## Step counts Three, four or five steps. Five is the cap and the file has no sixth. Specimen: Steps 3, 4 and 5, all at Current 2. the connector takes the difference: 84, 42.667, 22. Every row is 272, so only the connector moves: 84 at three steps, 42.667 at four, 22 at five. Past five the component stops: a sixth would drop the connectors under 12 and the row would read as a dotted line. The description sends you to a Progress Bar with a step counter. ## Progression The same five steps at each position. The check replaces the number the moment a step is behind you. Specimen: Current 1 to 5 at Steps 5. done green, current brand, upcoming soft. The connector into the current marker is already brand coloured, so the line marks the step you are on, not the one you finished. There is no all done state: at Current 5 the fifth marker is still current, so a finished checkout needs something else on screen to say so. ## Properties Two axes, twelve variants. No text, no icons, no slots. | Property | Type | Default | | --- | --- | --- | | `Steps` | 3 \| 4 \| 5 | 3 | | `Current` | 1 \| 2 \| 3 \| 4 \| 5 | 1 | | Set | Variants | Size | | --- | --- | --- | | `Progress Steps` | 12, Steps crossed with Current | 272 x 24, in all twelve | The grid is triangular, not square: Current cannot be higher than Steps, so three of the fifteen combinations do not exist. The numbers inside the markers are plain text with no property on them, so a flow that does not start at 1 is an override. There are no labels either. ## Tokens Five values, and one of them is the reason to look twice. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | Current marker, connectors behind it | | `state/success/base-500` | #22C55E | #16A34A | Done markers | | `bg/soft-100` | #E9E9EA | #48484A | Upcoming markers, connectors ahead | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Upcoming numbers | | `text/on-color/strong` | #FFFFFF | #FFFFFF | Current number, and icon/on-color on the check | Success is the only token here that gets darker in dark theme while everything around it gets lighter, and the white check sits on that darker green. Check that pairing on a real screen. The done marker is success green but the line joining done markers is brand blue. ## Guidelines - Do: Five is the ceiling. At five the connector is still 22 wide and the row reads as a path from one circle to the next. - Avoid: Do not add a sixth. The row stays 272, so every connector drops to 9.6 and the path turns into dashes between dots. Use it when the number of steps is known before the user starts and will not change while they are inside. Checkout, onboarding, a form split into parts. Past five, use a Progress Bar with a step counter in text. Decide the count before you show the first step. --- --- title: Radio description: Choose exactly one option from a set. The Radio in Appetite UI for Figma: every state, label handling and the tokens each one reads. group: Components source: https://www.appetiteui.com/docs/radio --- # Radio Radio is a form control for choosing exactly one option from a set. ## Preview One choice out of a visible set. Every option stays on screen and exactly one is filled. On iOS this is a Picker in inline style, on Android a RadioButton group. Specimen: A live group at Align Left, one selected. row 340, radio 44, content 280 at gap 16. Click one, or tab to the group and use the arrow keys. The whole row is the target: the control is 44 of tap target around a 28 disc, so the row clears the minimum even though the visible circle is smaller. Content takes the remaining 280 and wraps rather than pushing the radio off the row. A radio never ships alone, because a single one cannot be turned off again, which is why the preview is a group. ## States Three states crossed with one boolean, six variants. Two circles do all of it. Specimen: Default, Pressed and Disabled, each unselected then selected. 44 target, 28 disc, centre 24 or 12. The ring is not a stroke. It is a 24 circle in `bg/white-0` on a 28 disc, and the ring is the disc showing round the edge. Selecting shrinks that circle to 12. Pressed while unselected turns the disc brand blue, the only unselected radio in the set that is blue. Disabled while unselected has no centre circle, a solid grey disc rather than a grey ring. These six are there to be read, not operated, so nothing here takes a click. ## Label row The radio with a title, an optional description, and a side to sit on. The whole row is the tap target. A group of them is how the control actually ships. Specimen: Align Left and Align Right. 340 row, 44 radio, 280 of content, gap 16. Specimen: Show Description off, the same two aligns. title 18/28 Semi Bold, description 14/20 Regular. Align moves the radio from one end to the other, nothing else changes. Turning the description off drops the 14/20 line, and the 44 target sets the floor the row cannot go under. The title is 18/28 Semi Bold, larger than the 16 a settings row uses, so a long option name eats the width fast. Both rows in each frame stay selected on purpose: they are one variant shown twice, not two options. ## Properties Two axes on the control, four knobs on the label row. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Pressed \| Disabled, on Radio | Default | | `Active` | True \| False, on Radio | False | | `Align` | Left \| Right, on _Radio Label | Left | | `Label`, `Description` | Text, on _Radio Label | Title of radio | | `Show Description` | Boolean, on _Radio Label | True | | Set | Variants | Size | | --- | --- | --- | | `Radio` | 6, State crossed with Active | 44 x 44 | | `_Radio Label` | 2, on Align | 340 wide, height hugs | There is no group component. A radio group is however many label rows you place, and keeping exactly one Active is your job. Nothing carries a name or a value, so the wiring lives in code. There is no Hover: State covers Default, Pressed and Disabled only. ## Tokens Six values, and the pressed blue is the one that behaves oddly. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | Selected disc, and the pressed unselected one | | `brand/dark` | #193CB8 | #82BFFF | Disc when selected and pressed | | `bg/soft-100` | #E9E9EA | #48484A | Unselected disc, and both disabled ones | | `bg/white-0` | #FFFFFF | #0E0E0F | The centre circle, ring and dot alike | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Description | Pressed reverses direction between themes: in light `brand/dark` steps down from the base, in dark it steps up. More contrast either way, but pressed is not simply darker and you cannot fake it with a filter. The centre circle is `bg/white-0`, not transparency, so an unselected radio on any other surface shows a disc of the wrong tone. ## Guidelines - Do: Exactly one filled, and every option on screen. If the user cannot see all the choices at once, the control is a Dropdown or a Picker, not this. - Avoid: Two filled is a Checkbox wearing the wrong shape. Round means one, square means any number, and people read that before they read the label. Use it for two to five options that fit on screen together and deserve to be compared. One option is not a choice, and past five a list or a picker wins. Always start with one selected, and never use a radio for an action. --- --- title: Rating description: Shows a score out of five and collects one. The Rating in Appetite UI for Figma: display and interactive, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/rating --- # Rating Rating shows a score out of five, and collects one when someone is asked to give it. ## Preview Stars in two jobs. Display reports a score you cannot change, Interactive collects one. Neither iOS nor Android ships a rating control, so both are yours to build. Specimen: Display at 4.2, then Interactive at 3. 16 stars at gap 2, 36 stars at gap 8. Display sits inline beside a name or a place. Interactive stands alone and expects room around it. The score reads 4.2 while four stars are filled, because stars cannot draw a decimal. ## Display Read only, inline, five variants. Stars, a score, and a count you can turn off. Specimen: Rating 5, 4, 3, 2 and 1. rows measure 156, 152, 144, 146 and 136. The star row is a fixed 88 whatever the score, so only the text after it changes the width. Empty stars here are grey silhouettes, not outlines. Until 7 Sep 2026 the three star variant had no count node, so the count came and went with the variant. It has one now, and visibility is a Show Count boolean. ## Interactive The input version. 212 by 84, five 36 stars, and a hint that names the score in words. Press the stars. Specimen: Selected 0 through 5. 212 x 84, stars 212 x 36 at gap 8. Empty stars switch technique here: the same path stroked at 2 rather than filled, so it reads as an outline. That is deliberate, and it makes Interactive look lighter than Display stars blown up. The hint names every step of the scale. Stars are drawn at 36, and the component description asks for a 44 target on iOS and 48 on Android, so the target is yours to add. ## Properties Three axes and one boolean, eleven variants. Two of the axes only apply to one style each. | Property | Type | Default | | --- | --- | --- | | `Style` | Display \| Interactive | Display | | `Rating` | 1 \| 2 \| 3 \| 4 \| 5 \| 0 | 5 | | `Selected` | None \| 0 \| 1 \| 2 \| 3 \| 4 \| 5 | None | | `Show Count` | Boolean, bound to the count on all five Display variants | true | | Combination | Reads | Size | | --- | --- | --- | | Display, Rating 1 to 5 | Selected is always None | 136, 146, 144, 152, 156 x 28 | | Interactive, Selected 0 to 5 | Rating is always 0 | 212 x 84 | Rating holds the value for Display, Selected holds it for Interactive, and each is dead in the other style. Widths move with the text, not the stars: the star row is 88 in every Display variant, so 3 at 144 is narrower than 4 at 152 and wider than 2 at 146. Score, count and hint are text overrides, so a real number means editing the instance. There is no half star: 4.6 shows four stars and the number carries the rest. ## Tokens Four values, and the gold is the odd one out. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `colors/yellow/400` | #FFB900 | #FFB900 | Filled stars, one value in both modes | | `bg/soft-100` | #E9E9EA | #48484A | Empty stars, as a fill in Display and a 2 stroke in Interactive | | `text/strong-950` | #0E0E0F | #FFFFFF | The score | | `text/sub-700` | #48484A | #C7C7CC | The count and the hint | The gold is `colors/yellow/400`, a primitive with a single mode, so it stays #FFB900 while everything around it themes. It is the only raw primitive here, and there is no semantic token to reach for if you want a second gold. One token, two techniques: the empty star is a solid silhouette in Display and the same path stroked at 2 in Interactive. ## Guidelines - Do: Put the number next to the stars. Four stars could be anything from 3.5 to 4.4, and the count is what tells the reader whether to believe it. - Avoid: Stars alone are a decoration. Without a count, a perfect score from two people outranks a good one from two thousand, and nobody can tell. Use Display where a score is one attribute among several. Use Interactive only where rating is the point of the screen, and give every star a 44 target on iOS, 48 on Android. Turn Show Count off rather than emptying the text, so the row closes up instead of leaving a gap. Do not fake a half star by scaling the glyph; the set has no half state. --- --- title: Search description: Find content by typing, with an optional voice input. Search in Appetite UI for Figma: states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/search-field --- # Search Search lets people find content by typing a query, with voice input when the keyboard is in the way. ## Preview A search bar for the top of a screen or the first row of a list, with the microphone as an optional second way in. Type in it. Specimen: State follows what you type. 48 tall, radius 18, padding 16, width fills the parent. Empty is State Default, anything typed is State Searched. CSS reads the field's content; no script decides. Three things move at once: the fill, the 1px edge, and soft token to strong. The trailing icon goes mic to x, and the x clears the field. The component fills its parent, so the 332 here comes from the variant frame. The bar stays on the two content states: Active is a caret state and a real input already draws its own caret. ## States Three variants. Empty is a filled pill with no edge, filled in is a white bar with one, and the third is the bar while a finger is in it. Specimen: State Default, Searched, Active, all at Style Field. bg/weak-50 no border, bg/white-0 with 1px, bg/white-0 with 1.5px brand. Default to Searched: the fill goes weak grey to white, a 1px border appears, and the icon and text step from the soft token to the strong one. The trailing icon changes job too, the microphone offers another way to start and the x clears what is there. Active is the third: the same white bar, but the edge is 1.5 and brand blue, and the text node is replaced by a Typed frame holding the query and a 2 by 22 caret two apart from it. ## Style The second axis. Field sits inline in a form or a header. Hero floats over a photographic header on a home screen, and ends in a real button rather than an icon. Specimen: Style Hero, all three states. 345 x 60, radius 30, padding 18 and 8, gap 10, dropShadow/lg. Hero is 12 taller, 13 wider and carries a shadow, so it reads as an object sitting on the page rather than a row cut into it. The leading search icon drops 24 to 20, and the trailing 24 icon becomes a 44 icon-only Button on bg/strong-950 holding a 22 sliders-horizontal. Two things to know. Hero has no Show Mic, because it has no mic. And Hero's Searched changes only the text colour, where Field's Searched also changes the fill, the edge and the trailing icon, so the same state name means different things on the two styles. ## Trailing icon One boolean, wired to one variant. The 20 before it is a node in the file, not a gap. Specimen: Show Mic true and false, both at State Default, Style Field. the query row is the only thing that stretches. Specimen: State Searched, where the boolean is not wired. the x is drawn whatever Show Mic says. Show Mic hides the microphone in the default state and nothing else. The searched variant carries no property reference, so the boolean does nothing there, and Hero has no mic at all. When the mic goes the query row takes the 24 back, but the empty 20 slot stays, so the text ends 20 short of the padding. That slot is an unfilled rectangle in the file. Hero solves the same problem a different way, with a flexible Spacer that exists only in its Active variant. ## Properties Two axes, one boolean and one text property. Six variants, and the boolean reaches exactly one of them. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Searched \| Active | Default | | `Style` | Field \| Hero | Field | | `Placeholder` | Text | Search | | `Show Mic` | Boolean, bound to the mic in Default and Field only | True | | Part | What it is | Size | | --- | --- | --- | | `Query row` | Field only. Icon and text, the only child that stretches | fills, 256 at 332 | | `Icon slot` | Field only. An empty rectangle, no fill and no stroke | 20 x 20 | | `Typed` | Active only. The query and a caret, 2 apart | hugs, caret 2 x 22 | | `Spacer` | Hero and Active only. An empty flexible frame | fills, 1 tall | | `Action` | Hero only. An icon-only Button on bg/strong-950 | 44 x 44, icon 22 | | `mic` or `x` | Field only. Lucide at stroke 2, swapped by variant | 24 x 24 | Placeholder is a real text property, so the word in the bar is set on the instance rather than overridden on the layer. The entered value is not: the same text node carries both, at one style, 18/28 Semi Bold, heavier than most placeholders. There is still no loading or disabled state; add those yourself. ## Tokens Eight colours and one effect style, and several of the pairs are the same value under two names. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/weak-50` | #F4F5F5 | #242427 | The Field bar in Default | | `bg/white-0` | #FFFFFF | #0E0E0F | Field in Searched and Active, and every Hero | | `border/soft-100` | #E9E9EA | #2F2F31 | The 1px inside edge, Field Searched and Hero | | `brand/base` | #155DFC | #2B7FFF | The 1.5px Active edge and the caret | | `bg/strong-950` | #0E0E0F | #FFFFFF | The Hero action button | | `fg/white-0` | #FFFFFF | #0E0E0F | The sliders icon on that button | | `text/soft-500` | #8E8E93 | #AEAEB2 | Placeholder and search icon, with fg/soft-500 on the mic | | `text/strong-950` | #0E0E0F | #FFFFFF | Query text and icons once there is a query | The icons read `fg/soft-500` and `fg/strong-950`; the text beside them reads `text/soft-500` and `text/strong-950`. Each pair resolves to the same primitive in both modes, so the distinction is naming, not colour, and one CSS property covers both. `bg/strong-950` and `fg/white-0` are an inverting pair, which is why the Hero button is a black circle with a white icon in light and a white circle with a black icon in dark. The Hero shadow is the `dropShadow/lg` effect style, two drop shadows: 0 12 18 at spread -3 on black 8 percent, and 0 5 6 at spread -5 on black 5 percent. ## Guidelines - Do: Keep the placeholder to a word or two. The search icon and its gap take 32 before the text starts, and the slot and mic take another 44 after it. - Avoid: Do not write a sentence in the field. At 18/28 Semi Bold it truncates in the middle of the promise and the half that survives is the half nobody needed. Field goes in the Top Bar or at the head of a list, never floating in the middle of content. Hero is the one exception the file draws, and it is a narrow one: a home screen header, over an image, with the action button doing something real. Turn Show Mic off unless voice input actually works; an icon that opens nothing costs a target. Neither the x nor the action button is wired to anything, so clearing the field and handling the action are code you write. --- --- title: Segmented Control description: Switches between two to five views of the same content. The Segmented Control in Appetite UI for Figma: 2 sizes, every state and its tokens. group: Components source: https://www.appetiteui.com/docs/segmented-control --- # Segmented Control Segmented Control switches between two to five views of the same content. ## Preview A mutually exclusive switch between views of the same content, applied the moment it is picked. Default size, three segments, icon on. The specimen is live, so pick one. Specimen: Segmented Control, Size Default, Tabs 3. track 340 x 48, padding 4, segment 110.67 x 40, radius full. The published component exposes exactly two properties, Size and Tabs: no text, no icon switch, no way to say which segment is on. Every label reads Option because the label lives on a nested selector instance, and so do Active, Icon and Icon Swap. Building a real control means reaching into each segment one at a time. The count ships as a variant, not a slot. ## Tabs Two to five segments in the same 340 track. The 4px padding never moves, so segments split the remaining 332 evenly; only their width changes. Five is the cap, the same as Tabs. Specimen: Tabs 2, 3, 4, 5. 332 split evenly, at Size Default with Icon off. At Default a segment spends 16 of padding on each side, so five leave 34.4 for the label. Icon is a boolean, and this run has it off. The track is pinned to 340 and fixed on both sides, and each segment fills an equal share of the 332 left over: 166, 110.67, 83 and 66.4 wide. Nothing else differs between the four counts, which is why they ship as a variant rather than as layout. ## Size Two heights, 48 and 38. The segment carries the change: 40 tall at padding 8 and 16 becomes 30 at 6 and 12, label 16/24 to 14/20 at letter spacing .1. Track padding, gap and radius hold. Specimen: Size Default, Size Small. track 48 / 38, segment 40 / 30, label 16 / 14. The track carries neither height directly: 48 and 38 are what you get once a 40 or a 30 segment sits inside 4 of padding. The icon does not move at all, 16 square in both, so at Small it takes a bigger share of a shorter segment. Small is the size to drop the icon on, not the one to keep it. ## State One state, held by exactly one segment. Active takes the bg/white-0 fill, dropShadow/xs and text/strong-950. Inactive carries no fill and drops to text/sub-700. The icon holds fg/soft-500 either way. Specimen: Active True, Active False. fill bg/white-0 with dropShadow/xs, then no fill. The file cannot enforce the one-active rule. Active lives on the nested selector, so a three segment variant carries three independent booleans and nothing stops all three being on at once. That is a rule for your code, not for the component. An unselected segment shows the track straight through, so the label and the pill behind it carry the selection on their own. ## Properties Two variant axes on the published component. The rest sit on the selector inside it, a building block not meant for direct use. | Property | Type | Default | | --- | --- | --- | | `Size` | Default \| Small | Default | | `Tabs` | 2 \| 3 \| 4 \| 5 | 2 | | `Active` | True \| False, on the selector | True | | `Icon` | Boolean, on the selector | true | | `Icon Swap` | Instance swap, on the selector | circle | Segmented Control ships 8 variants, 2 sizes across 4 tab counts. The selector ships 4, active across size. The top two rows are the only things an instance can set from the outside; everything below them lives one level down. The count is a variant, the state is not, and the label is neither. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/weak-50` | #F4F5F5 | #242427 | The track | | `bg/white-0` | #FFFFFF | #0E0E0F | The active segment | | `text/strong-950` | #0E0E0F | #FFFFFF | The active label | | `text/sub-700` | #48484A | #C7C7CC | Every other label | | `fg/soft-500` | #8E8E93 | #AEAEB2 | The icon, in both states | Radius is `dimensions/radius/full` on both track and segment. The active fill carries `dropShadow/xs`: 0 1px 2px at 4 percent over 0 4px 4px at 1 percent. Accordion and Card reach for the same pair, a fair argument for an elevation token. Five colours, no raw primitives. In dark, bg/weak-50 lands on #242427 while the active segment lands on #0E0E0F, so the selected pill is darker than the groove holding it. ## Guidelines - Do: Keep the set short and the labels to one word. Segments take an equal share, so the longest label decides the room every other segment gets. - Avoid: Do not push five long labels through a Default track. Each segment gets 66.4 and spends 32 on padding, so the words clip. Go Small, drop the icon, or use Tabs. Reach for Tabs when the switch belongs to the content below it, and for this when it belongs to a setting. Both cap at five. Segmented Control is a pill in a groove, iOS-native, applied on pick. Tabs is an underline with no track at all. Same job, opposite chrome, so pick one per screen and do not run both. --- --- title: Skeleton description: Holds the shape of content while it loads. The Skeleton in Appetite UI for Figma: shapes, pulse, tokens and when not to use one. group: Components source: https://www.appetiteui.com/docs/skeleton --- # Skeleton Skeleton holds the shape of content while it loads, so the screen does not jump when it arrives. ## Preview A placeholder that holds the shape of what is coming, so the screen does not jump when the data lands. For lists and cards this beats a spinner. Specimen: _Skeleton List Row, repeated three times. 360 x 72, circle 40, lines at gap 8, pulse 480ms. One row is a 40 circle, a line that fills the rest and a short line under it, all breathing together. The description says to stack three to five while data loads. The pulse runs 480ms on a linear curve, the timing the file names `motion/skeleton/pulse`. ## Types Three shapes and nothing else. No text, no icons, no content of any kind. Specimen: Line, Circle and Block. 200 x 12, 40 x 40, 200 x 120. Line and Circle are both radius full, so a 12 tall line is a stadium and a 40 square is a disc. Block is the only one on a real radius token, so it reads as a card rather than a pill. Every size here is a default meant to be resized on the instance. ## Pulse Two variants that are the two ends of one breath. Held still here so you can see both. Specimen: Pulse 1 then Pulse 2, at Line, Circle and Block. bg/sub-200 to bg/soft-100. Pulse is a timeline, not a state. Both ends move the same way in either theme: light runs #D1D1D6 down to #E9E9EA, dark #636366 down to #48484A, so it always breathes from more contrast to less, never lighter to darker. In code, animate between two colours and alternate. ## Properties Two axes, six variants, and one ready made row beside them. | Property | Type | Default | | --- | --- | --- | | `Type` | Block \| Circle \| Line | Line | | `Pulse` | 1 \| 2 | 1 | | Component | Variants | Size | | --- | --- | --- | | `Skeleton` | 6, Type crossed with Pulse | 200 x 12, 40 x 40, 200 x 120 | | `_Skeleton List Row` | not a set, no properties | 360 x 72 at radius 4 | The file describes this row as matching the List component. It does not. List is 340 by 76 at radius 24, gap 12, tile at radius 12, 224 of content. This is 360 by 72 at radius 4, gap 16, circle at radius full, 272 of content. Six numbers, none shared, so a stack of these does not hold the shape of the list. That radius 4 is also the one raw value on the page. ## Tokens Two greys and one piece of motion. That is the whole component. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/sub-200` | #D1D1D6 | #636366 | Pulse 1, the stronger end of the breath | | `bg/soft-100` | #E9E9EA | #48484A | Pulse 2, the weaker end | | Motion token | Resolves to | Value | | --- | --- | --- | | `motion/skeleton/pulse/duration` | motion/duration/slower | 480ms | | `motion/skeleton/pulse/easing` | motion/easing/linear | cubic-bezier(0, 0, 1, 1) | Linear is right here and unusual everywhere else in the system: a breath that eases reads as a thing being tapped, not a thing waiting. Neither grey is a semantic loading token. They are the background tokens used for dividers and empty states, so a skeleton on `bg/soft-100` loses one end of its pulse. ## Guidelines - Do: Repeat the row and let it hold the shape of the list underneath. Three to five is the range the file suggests, and nothing jumps when the data arrives. - Avoid: Do not stand one grey block in for a list. It promises a shape the content will not have, so the layout jumps anyway and it says less than a spinner would. Reach for it when the layout is known and the wait is short: lists, cards, a profile header. Match the skeleton to the real thing and resize the instance rather than accept the defaults. Do not skeleton a whole screen; a page of grey is harder to read than one spinner. Cap it: past about ten seconds the honest thing on screen is an error state. --- --- title: Slider description: Set a value or a range by dragging along a track. The Slider in Appetite UI for Figma: values, range, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/slider --- # Slider Slider lets people set a value, or a range, by dragging along a track. ## Preview Continuous value selection across a known range. A label, an amount, and a 340 track with one handle on it. Specimen: Percentage 50, label and amount on. 340 x 40, track 6 tall, handle 16. The label hugs left and the amount fills right, so the row runs the full width. The handle is a white circle with a brand dot inside it, lifted off the track by `dropShadow/xs`. Drag it. ## Values Five variants, each a progress width against the same 340 track. Specimen: Percentage 0, 25, 50, 75 and 100. progress 1.06, 85, 170, 255, 340. The middle three put the handle centre on the end of the progress line. The two extremes pull 3 inward, which keeps the overhang equal at both ends. Nothing clips, so the drawn component is 349 wide, not 340. At 0 percent the track is not empty: the line is 1 wide, a real node that rounds to nothing. ## Range A second set with a handle at each end. Eleven of the twenty combinations exist. Specimen: Left 0 with Right 0, then 0 to 25, 25 to 50, 50 to 75, 75 to 100. the first one is the default variant. Specimen: Wider spans: 0 to 50, 25 to 75, 50 to 100, 0 to 75, 25 to 100, 0 to 100. the same two handles further apart. Left 0 with Right 0 is the default on both axes and draws a full track, so an empty range looks identical to a complete one the moment you place the component. Four of the eleven variants put a handle 3 off the bound it marks. Both are drawn as the file has them, which is why these are the only sliders on the page you cannot drag. ## Properties Two sets, three axes, and the same three knobs on both. | Property | Type | Default | | --- | --- | --- | | `Percentage` | 0% \| 25% \| 50% \| 75% \| 100%, on Slider | 0% | | `Left Range` | 0% \| 25% \| 50% \| 75% | 0% | | `Right Range` | 0% \| 100% \| 25% \| 50% \| 75% | 0% | | `Label` | Boolean, on both | True | | `Label Text`, `Amount Number` | Text, on both | Label, 100 | | Set | Variants | Size | | --- | --- | --- | | `Slider` | 5, on Percentage | 340 x 40 | | `Range Slider` | 11 of 20, Left crossed with Right | 340 x 40 | Value is a five step axis, not a number, so anything between the quarters is an override. There are no states: no pressed, no disabled, no focus. The handle is 16 against the 44 a finger needs, so you add the hit area around a target the file never draws. ## Tokens Five colours and one effect style. The handle is the interesting one. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | Progress line and the dot inside the handle | | `bg/soft-100` | #E9E9EA | #48484A | The track behind it | | `static/static-white-0` | #FFFFFF | #FFFFFF | The handle, one value in both modes | | `text/strong-950` | #0E0E0F | #FFFFFF | Label | | `text/sub-700` | #48484A | #C7C7CC | Amount | The handle reads `static/static-white-0` rather than a background token, so it stays white on the dark theme instead of turning near black. That is right for a knob on the brand line, and the one place a static colour belongs here. It floats on `dropShadow/xs`. ## Guidelines - Do: Keep the amount on screen. A handle three quarters along says nothing by itself, and the number is the only part of this component that names the value. - Avoid: Do not turn the label off and leave the track on its own. Without a readout the user is guessing, and a slider nobody can read is decoration. Reach for it when the direction matters more than the exact number: volume, brightness, a price filter. When the value has to be exact, a stepper or a field beats it. Update the amount live as the handle moves; the number is the feedback. Use the range set for genuine bounds, not two unrelated values sharing a scale. --- --- title: Tab description: Switches between related views inside one screen. The Tab in Appetite UI for Figma: sizes, states and the tokens each one reads. group: Components source: https://www.appetiteui.com/docs/tab --- # Tab Tab switches between related views inside a single screen. ## Preview An in-page switch between sibling sets of content. It changes what sits below it, never the top-level destination. Default size, three tabs, icon on. Live specimen, pick one. Specimen: Tabs, Size Default, Tabs 3. track 340 x 48, padding 0 and 4, tab 110.67, rule 2. Same job as Segmented Control, a different component underneath. No track: the outer frame has no fill, no radius, side padding only, so this is three tabs and one rule. Like Segmented Control, the parent exposes only Size and Tabs; label, icon and which tab is on live on nested selector instances. Every tab reads Option. ## Tabs Two to five tabs in the same 340 track. The 4px side padding never moves, so tabs split the remaining 332 evenly; only width changes. No rule across the track, only under the active tab. Specimen: Tabs 2, 3, 4, 5. 332 / n at Size Default, Icon off. Tabs ships 8 variants, 2 sizes across 4 tab counts. At Default a tab spends 16 of padding per side, so five leave 34.4 for the label. Widths match Segmented Control, 166, 110.67, 83 and 66.4, both split the same 332. Long labels differ: Segmented Control clips inside a visible pill, this clips against nothing, so five real words run together with no edge between tabs. ## Size Two heights, 48 and 36. The tab fills the track, so only padding and label change: 12 and 16 with a 16/24 label becomes 8 and 12 with 14/20 at letter spacing .1. Rule stays 2, icon stays 16. Specimen: Size Default, Size Small. track 48 / 36, padding 12 / 8, label 16 / 14. 48 and 36, where Segmented Control is 48 and 38. They line up at Default and sit two apart at Small, because this component has no 4px track padding to absorb. Everything else is the tab: padding 12 and 16 down to 8 and 12, label 16/24 down to 14/20. Rule holds at 2, icon holds 16, so at Small the glyph takes a bigger share of a shorter tab. ## State One state, held by one tab. Active draws a 2px brand/base rule inside its own bottom edge and turns label and icon brand/base. Inactive has no rule, a text/sub-700 label and an fg/soft-500 icon. Specimen: Active True, Active False. 2px rule brand/base inside, then nothing. Exactly one tab should hold it and, as on Segmented Control, the file cannot enforce that: Active lives on the nested selector, so every tab carries its own boolean. The defaults differ on paste: this selector defaults Active to False, the Segmented Control one to True. The rule is a stroke on the bottom edge alone, which Figma reports as a mixed stroke weight, not a border. Read it as an underline. ## Properties Two variant axes on the published component. The rest sit on the selector inside it, a building block not meant for direct use. | Property | Type | Default | | --- | --- | --- | | `Size` | Default \| Small | Default | | `Tabs` | 2 \| 3 \| 4 \| 5 | 2 | | `Active` | True \| False, on the selector | False | | `Icon` | Boolean, on the selector | true | | `Icon Swap` | Instance swap, on the selector | circle | The selector ships 4 variants, active across size. It is composed into every Tabs variant, so a change there reaches all eight. Two names need tidying: the set is called Tabs while this page, the sidebar and the URL say Tab, and the frame inside the selector is still called _SegmentedControl-selector, left over from the component this was copied from. Neither changes a pixel; both cost a minute in search. ## Tokens Every value the component reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `brand/base` | #155DFC | #2B7FFF | The rule, the active label and the active icon | | `text/sub-700` | #48484A | #C7C7CC | Every other label | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Every other icon | Radius is `dimensions/radius/none` on the track and on the tab. No fill and no shadow anywhere, which separates it from Segmented Control. Three colours is the whole palette, and brand/base does three jobs: the rule, the label and the icon. On the sibling the icon holds fg/soft-500 whether the segment is on or not. Here selection reaches every part of the tab, and the rule still marks it when the colour does not. ## Guidelines - Do: Use tabs for sibling content on one screen. The tab swaps what sits under it and leaves the rest of the screen where it was. - Avoid: Do not move between top-level destinations with tabs. That is Navigation, and a tab that replaces the whole screen leaves no way back. The component description carries the whole rule: this does not change the top-level destination, Navigation does. The second rule is against Segmented Control. Both switch between two and five things, both are 48 tall at Default, and they are not interchangeable: a segmented control is a setting applied on pick, tabs belong to the content below them. Pick one per screen. --- --- title: Toast description: Reports what just happened without blocking the screen. The Toast in Appetite UI for Figma: 5 types, states, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/toast --- # Toast Toast reports what just happened without blocking the screen, then takes itself away. ## Preview Transient feedback that does not block input and takes itself away. On Android this is a Snackbar, on iOS a custom overlay. Specimen: Type Line at State Neutral, then Type Long at State Success. 320 wide, 36 and 112 tall, radius 24, dropShadow/sm. Both types are the same card. Line is one row with a title, an action and a dismiss. Long adds a description under the title and stacks the two controls on the right. ## Types Two shapes for two amounts of message. The type scale changes with them. Specimen: Type Line then Type Long, both at State Neutral. icon 16 and 20, title 12/16 and 14/20. The difference is not only height. Line sets the title at 12/16 next to a 16 icon; Long lifts it to 14/20 next to a 20 icon and lets the description run to three lines, taking the card to 112. ## States Five states, one icon. State moves the icon colour and nothing else. Specimen: Neutral, Error, Success, Warning and Info, at Type Line. the card stays bg/white-0 in all five. All ten variants carry the same icon, a speech bubble, so an error here looks like a message rather than a warning. Swapping it is on you. The surface never changes either: no tint, no coloured border, just the icon. Info holds #155DFC in both themes while the button beside it moves to #2B7FFF, so two blues that match in light come apart in dark. ## Properties Two axes, ten variants, and five knobs on top of them. | Property | Type | Default | | --- | --- | --- | | `Type` | Long \| Line | Line | | `State` | Neutral \| Error \| Success \| Warning \| Info | Neutral | | `Icon`, `Link Button`, `Dismiss Icon` | Boolean | all True | | `Icon Swap` | Instance swap, 1,667 preferred icons | message-circle | | `Title Edit`, `Description` | Text | Title of toast | | Part | What it is | Size | | --- | --- | --- | | `Button` | A Button instance, Brand Link sm | 85 x 36 | | `Compact Button` | Transparent, holding a 20 x at stroke 2.5 | 32 x 32 | Description is a property on both types even though only Long draws it, so setting it on a Line toast changes nothing. The action is a real Button instance, not a text node, so its label is an override two levels down. There is no position, stacking or duration: where it sits, how long it stays and what a second arrival does are all yours. ## Tokens Four for the card and five for the icon. Two of the five do not theme. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | The card, all ten variants | | `border/soft-100` | #E9E9EA | #2F2F31 | 1px inside border | | `text/strong-950` | #0E0E0F | #FFFFFF | Title | | `text/sub-700` | #48484A | #C7C7CC | Description, Long only | | `brand/base` | #155DFC | #2B7FFF | Action label | | `fg/soft-500` | #8E8E93 | #AEAEB2 | Dismiss x | | `state/faded/base-500` | #8E8E93 | #636366 | Neutral icon | | `state/error/base-500` | #FB2C36 | #FB2C36 | Error | | `state/success/base-500` | #22C55E | #16A34A | Success | | `state/warning/base-500` | #FF6900 | #F54900 | Warning | | `state/information/base-500` | #155DFC | #155DFC | Info, one value in both modes | Warning, success and faded step from their 500 to their 600 in dark, which keeps them off a near black card. Error and information do not move, so on the dark card information is the weakest colour here, sitting next to a button that moved the other way. ## Guidelines - Do: Keep the action optional. Undo, View, Retry: things the user can ignore and lose nothing by, because the toast is going to leave on its own. - Avoid: Never put a required action in a toast. It dismisses itself, so a Confirm that lives only here is a decision the user can miss by looking away. Use it for feedback about something that already happened, not for a question. Keep the title to one line, and reach for Long only when the extra sentence changes what the user would do. Swap the icon to match the state; the file ships all ten with a speech bubble. Put a required decision in a Modal or an Alert, never here. --- --- title: Toggle description: Turns a single setting on or off, applied immediately. The Toggle in Appetite UI for Figma: 3 states, label rules and the tokens behind them. group: Components source: https://www.appetiteui.com/docs/toggle --- # Toggle Toggle turns a single setting on or off, and applies the change immediately. ## Preview Default, active. A 44 by 28 track carrying a 22px handle, centred in a 44px target, so the thing you can hit is bigger than the thing you can see. Click it. Specimen: State Default, Active True. 44 x 28 track, radius 9999, 22 px handle, 16 px of travel. The 44 frame is the target and the control inside it is 44 by 28, sitting 8 from the top and 8 from the bottom, so the width is identical and only the height has slack. The handle is a 22 circle inset 3 on every side, which puts it at 3 when off and 19 when on: 16 of travel. Two shadows sit under it, both black, one at 4 percent and one at 1. ## State Three states across two active values. Pressed shifts the track while the finger is down, so the track confirms the touch before the value changes. Specimen: State x Active. Default, Pressed, Disabled, each off then on. Six variants, State by Active, no holes, and the track fill carries all of it. Off is bg/soft-100, on is brand/base, and pressed steps each along its own ramp: bg/sub-200 when off, brand/dark when on. Disabled ignores Active and paints bg/soft-100 both ways, so only handle position separates a disabled on from a disabled off. The handle drops both shadows there too. One drift: pressed while on puts the handle at 19.25 by 2.75 where every other variant has it at 19 by 3. ## Label The label is its own component, so the track stays a track. Align puts it on either side, and the description can be switched off. All three switch. Specimen: Align Left, Align Right. 340 x 48, gap 16, title 18/28, description 14/20. The row is 340 by 48: a 44 control, 16 of gap, 280 of text. The height comes from the text, not the control, 28 for the title at 18/28 Semi Bold plus 20 for the description at 14/20, so switching Show Description off takes the row to 28 while the control stays 44. Align swaps the order of the two children, it does not flip a layout property, so the gap sits inside either way. ## Properties Two sets: the track, and the label that carries it. | Property | Type | Default | | --- | --- | --- | | `State` | Default \| Pressed \| Disabled | Default | | `Active` | True \| False | False | | `Align` | Left \| Right | Left | | `Show Description` | Boolean | true | | `Label` | Text | Label | | `Description` | Text | Insert the label description here. | Two component sets, not one. Toggle is six variants of State by Active and carries no text. _Toggle Label is two variants of Align and holds Label, Description and Show Description. The underscore keeps it out of the Assets panel, so place a Toggle and switch to the label variant to reach it. Its description asks for the whole row to be the tap target, more than the 44 the control draws. ## Tokens Every value the toggle reads, resolved through its aliases to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/soft-100` | #E9E9EA | #48484A | The track when off, and both ways when disabled | | `bg/sub-200` | #D1D1D6 | #636366 | The track pressed while off | | `brand/base` | #155DFC | #2B7FFF | The track when on | | `brand/dark` | #193CB8 | #82BFFF | The track pressed while on | | `bg/white-0` | #FFFFFF | #0E0E0F | The handle | | `text/strong-950` | #0E0E0F | #FFFFFF | Label title | | `text/sub-700` | #48484A | #C7C7CC | Label description | Four track fills and one handle. bg/sub-200 and brand/dark are not dimmed versions of the off and on fills, they are the next step along each ramp, so pressing moves the control forward instead of fading it. The handle reads bg/white-0 and themes with everything else, so a dark mode handle is #0E0E0F, a near black knob on a blue track. Disabled adds nothing of its own and reuses bg/soft-100. ## Guidelines - Do: Name the setting, then say what it does. A toggle takes effect the moment it moves, so there is nothing to save and nothing to confirm. - Avoid: Never label a toggle with a negative. Off then means on, and the reader has to solve a puzzle before changing a setting. Toggle applies the moment it moves; if the change needs a Save step, use Checkbox. A toggle names a state, not an action, so Weekly digest is right and Turn on weekly digest is not. The 44 target is drawn into the component; the label component expects the whole row to be tappable, and that part you add in code. --- --- title: Toolbar description: Holds the actions for the current screen. The Toolbar in Appetite UI for Figma: 2 to 4 actions, a primary, tokens and guidelines. group: Components source: https://www.appetiteui.com/docs/toolbar --- # Toolbar Toolbar holds the actions for the current screen, anchored to the bottom of it. ## Preview Actions for the current screen, anchored to the bottom. Two to four, plus an optional primary. Specimen: Actions 2 with Primary False, then Actions 4 with Primary True. 340 x 62, radius 0, a 1px border on the top edge only. The only stroke runs along the top and the corners are square: this is the bottom edge of a screen, not a card. Actions sit left, the right holds a filled primary or nothing. It is not Navigation. A toolbar changes with the screen, navigation does not. ## Actions Two, three or four. The row grows to the right, the bar does not move. Specimen: Actions 2, then 3, then 4, all at Primary False. row 92, 140 and 188 wide at gap 4, buttons 44 square. The icons ship in a fixed order, share-2, bookmark, folder and trash-2. Each is an instance swap. Replace them. The buttons are Neutral Link, Icon Only, so they carry no fill and no border: the 44 square is a target, not a visible shape. ## Primary One filled action on the right, or a one pixel spacer where it would have been. Specimen: Primary False then Primary True, both at Actions 4. primary 44 square at x 288, spacer 1 x 44 at x 331. The primary is the only filled thing on the bar, and on Android it is the FAB. Turning it off does not delete it: the file swaps in a 1 wide frame named Spacer, so space between still has something to push against. It renders as nothing. ## Properties Two axes, six variants, and five icon slots that are always present. | Property | Type | Default | | --- | --- | --- | | `Actions` | 2 \| 3 \| 4 | 2 | | `Primary` | False \| True | False | | `Icon 1` to `Icon 5` | Instance swap, 4 preferred values each | share-2, bookmark, folder, trash-2, plus | | Part | What it is | Size | | --- | --- | --- | | `Actions` | A hugging row at x 8, gap 4 | 92, 140 or 188 | | `Button`, action | Button instance, Neutral Link md, Icon Only | 44 x 44 | | `Button`, primary | Button instance, Brand Filled md, Icon Only | 44 x 44 | | `Spacer` | A frame, on the Primary False variants only | 1 x 44 | All five icon slots sit on every variant, so at two actions the third and fourth swaps stay in the panel and change nothing. No states: no pressed, no disabled, no selected. Build an unavailable action yourself. No label variant either, so the bar is icon only. ## Tokens Five colours. No shadow, no radius, no elevation. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/white-0` | #FFFFFF | #0E0E0F | The bar | | `border/soft-100` | #E9E9EA | #2F2F31 | The top edge, 1px inside | | `fg/sub-700` | #48484A | #C7C7CC | Action icons | | `brand/base` | #155DFC | #2B7FFF | Primary fill | | `static/static-white-0` | #FFFFFF | #FFFFFF | The plus, one value in both modes | No shadow, no radius. The bar is welded to the bottom of the screen; the top hairline is its only separation from the content above. On iOS it sits over the home indicator and the SDK takes over with Liquid Glass, so treat the flat fill as a placeholder, not a finished material. ## Guidelines - Do: One filled button, on the right, doing the thing this screen is for. Everything to the left of it is a supporting action on the same content. - Avoid: Do not fill a second button. Two filled circles is two primaries, and the bar stops telling anyone what the next step is. Use it for actions on the current screen, and move it out of the way when that screen changes. Section links belong in Navigation. Four actions is the ceiling; put the overflow in a Context Menu behind an ellipsis. Keep the destructive action away from the primary. The file draws 44, so give every target 48 on Android. --- --- title: Tooltip description: A short contextual hint anchored to an element. The Tooltip in Appetite UI for Figma: 4 positions, 2 anchor sizes and no hover on mobile. group: Components source: https://www.appetiteui.com/docs/tooltip --- # Tooltip Tooltip explains one control in a few words, anchored to the thing it describes. There is no hover on a phone, so it opens on tap. ## Preview A short hint anchored to the control it explains. No hover on a phone: it opens on tap, closes on the next. Keep it to a label and a line. Anything longer belongs in the screen, not over it. Specimen: Open True, Position Top, Size md, tap the icon. anchor 24, bubble 164 x 48, radius 12, padding 8 12. The component's own box is the 24 icon. The bubble sits outside that frame in the file and is absolute here, so opening one never moves anything around it. Both strings are the file's own. ## Position Four positions on one axis. The bubble clears the anchor by 8 on the side it points from and centres on the other. Nothing flips at a screen edge, so pick the side with room. Specimen: Top, Bottom, Left, Right. 8 gap on the pointing side, pointer 16 x 8. Top measures 164 wide, exactly what the two strings need. Bottom, Left and Right measure 173 for the same content, nine wider than it takes. Drawn as the file has it. ## Size Two sizes. The axis moves the anchor only: the bubble is the same object at both. Size sets the tap target, not the hint. Specimen: Size md then sm, both Open True. icon 24 and 16, bubble unchanged at 164 x 48. A 16 anchor is under the 44 minimum on its own, so at sm give the icon a larger tap target than the artwork, or hang the tooltip off a control that already has one. ## Content Two booleans on the bubble, both true by default. Turn the supporting line off for a one word label. Turn the headline off and you get a sentence in the muted colour, the weaker of the two. Specimen: Both, Headline only, Supporting Text only. each line 12/16, one line drops the box to 32 tall. The wrapper hugs its content, so dropping a line takes 16 off the height and leaves the width alone. The headline is the only line with weight: white on the fill, while the supporting line reads a muted grey. ## Properties Three axes on the published component, sixteen variants. The bubble is a private block with two booleans of its own. | Property | Type | Default | | --- | --- | --- | | `Open` | True \| False | True | | `Position` | Top \| Left \| Right \| Bottom | Top | | `Size` | md \| sm | md | | `Headline` | Boolean, on _Tooltip | true | | `Supporting Text` | Boolean, on _Tooltip | true | | Set | Variants | Size | | --- | --- | --- | | `Tooltip` | 16, Open x Position x Size | 24 x 24, or 16 x 16 at sm | | `_Tooltip` | 4, on Position | 164 x 56, 173 x 56, 181 x 48 | No text property anywhere. Both strings are plain text nodes, so real copy is an override on each instance. The published component measures the anchor only. A tooltip never changes the layout it sits in, and nothing stops it running off a narrow screen. ## Tokens Four colours, and two of them invert with the theme. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `bg/strong-950` | #0E0E0F | #FFFFFF | The bubble and its pointer | | `text/white-0` | #FFFFFF | #0E0E0F | Headline | | `text/disabled-400` | #AEAEB2 | #8E8E93 | Supporting line | | `fg/sub-700` | #48484A | #C7C7CC | The info anchor | The supporting line reads `text/disabled-400`, a disabled token doing the work of a supporting one, so a live sentence looks switched off. Drawn as the file has it. The pointer's two source rectangles are bound to `fg/strong-950` while the boolean result and the bubble read `bg/strong-950`. Both resolve to the same value in both modes, so that one is naming, not appearance. Geometry runs on `radius/lg` 12, `spacing/3` 12, `spacing/2` 8 and `icon/stroke` 2. No effect on the bubble, so a layer floating over content has only its own fill to separate it. ## Guidelines - Do: Define one term the screen cannot. A label and a line, and the answer is on the screen the moment the user asks. - Avoid: Do not put an action in it. A tooltip closes on the next tap anywhere, so anything you can press inside one is a target the user loses on the way to it. Use it where a control cannot say what it means in the space it has: an icon button, a metric, a legal term. Never hide anything the user needs to finish the task: on a phone a tooltip is a deliberate tap, not something that happens in passing. Check the side before you ship. Nothing flips away from a screen edge, so a Left tooltip near the left margin runs off the screen. --- --- title: Top Bars description: The screen header: title, back affordance and actions, in a compact and a large size. Anatomy, tokens and guidelines from the Appetite UI Figma library. group: Components source: https://www.appetiteui.com/docs/top-bars --- # Top Bars The header at the top of a screen: where you are, how to get back, and at most a couple of things to do here. Compact and large, plus the section headers that break up what is underneath. ## Preview The header at the top of a screen: where you are, how to get back, and at most a couple of things to do here. On iOS this is the NavigationStack toolbar, on Android a TopAppBar. Specimen: Size sm, Actions False, Show Caption and Show Back Chevron true. 340 x 64, padding 0 24, gap 12, back button 44. The bar has no fill of its own. Background Glass is hidden in all six variants, so out of the box a Top Bar sits on whatever is behind it. The title block takes what the actions leave, 236 here. ## Size Two sizes, two layouts, not one scaled. Compact keeps the title on the back button's row. Large gives the title its own row underneath, the pattern that collapses into compact as the user scrolls. Specimen: Size sm then lg, both Actions False. 64 in one row, 128 as a 60 nav row over a 68 title block. The title steps from 20/28 to 24/32 and the caption from 12/16 to 14/20: a different type pairing, not the same one enlarged. Large reads the Heading font family, compact reads Body. Both are Inter today, so the split costs nothing and is there when they diverge. ## Actions Three options on one axis: nothing, one filled button for what this screen is for, or up to two quiet ones. The title block shrinks to make room, so a long title truncates sooner. Specimen: Actions False, Main Button, Secondary Actions. title block 236, then 180, then 132. Main Button is brand/base under a white check. Secondary Actions is a 92 container at gap 4 holding two 44 circles on brand/alpha-10, brand blue at 10 percent in both themes, glyphs in brand/base. Two is the ceiling the component ships; a third would eat the title. ## Glass One boolean, off by default. Turn it on when the bar floats over scrolling content that must stay readable underneath. Leave it off on a plain background: glass over a flat colour is a blur with nothing to blur. Specimen: Glass true, over a hatch so there is something to see through. bg/glass at 72 percent under a 15 blur. The hatch is this page's ground, not the component's. In the file this is a Figma GLASS effect with refraction and dispersion, which CSS cannot express. Here it runs as the 72 percent fill (55 in dark) with a 15px backdrop blur: an approximation, not a match. Same substitution as Modal Sheet. ## Section Header A separate component for headers inside a screen, not above it. A title over a block of content with one optional link. Use it to break a long screen into groups the eye can scan. Specimen: Section Header, Link true then false. 340 wide, title 18/28 Semi Bold, link 36 tall. The component measures 340 by 24 in the file and does not clip, but its title is 28 tall and its link 36, so both hang outside the frame, the link by 6 at each end. Drawn here at the 36 the content needs. The title runs font size lg 18 on line height xl 28, one step apart, which is the extra 4. ## Properties Two variant axes and five instance properties on Top Bar. Section Header is its own component with two. | Property | Type | Default | | --- | --- | --- | | `Size` | lg \| sm | sm | | `Actions` | False \| Main Button \| Secondary Actions | False | | `Title`, `Caption Text` | Text | Title, Caption | | `Show Caption` | Boolean | true | | `Show Back Chevron` | Boolean | true | | `Glass` | Boolean | false | | `Section Title`, `Link` | Text, Boolean, on Section Header | Section Title, true | | Component | Variants | Size | | --- | --- | --- | | `Top Bar` | 6, Size crossed with Actions | 340 x 64, or 340 x 128 at lg | | `Section Header` | not a set | 340 x 24, content needs 36 | No status bar in this component and no search field, so a real header is this plus whatever the platform draws above it. The back affordance is a Button instance, so its states come from Button, not from here. ## Tokens Every value the two components read, resolved to the primitive underneath. | Token | Light | Dark | Applied to | | --- | --- | --- | --- | | `text/strong-950` | #0E0E0F | #FFFFFF | Title, and the Section Header title | | `text/soft-500` | #8E8E93 | #AEAEB2 | Caption | | `bg/weak-50` | #F4F5F5 | #242427 | Back button | | `fg/sub-700` | #48484A | #C7C7CC | Back chevron | | `brand/base` | #155DFC | #2B7FFF | Main button fill, secondary glyphs, the section link | | `static/static-white-0` | #FFFFFF | #FFFFFF | The check on the main button | | `brand/alpha-10` | #2B7FFF at 10% | #2B7FFF at 10% | Secondary action fills, one value both modes | | `bg/glass` | #FFFFFF at 72% | #242426 at 55% | Background Glass, hidden by default | `brand/alpha-10` is a single mode value, so the secondary buttons sit at 10 percent of the same blue on a black screen as on a white one, a much fainter pill in dark. Every glyph is drawn at 22 with a 2.2 stroke while `dimensions/icon/stroke` is 2: the 20 Lucide artboard was scaled to 22 and took its stroke with it. Geometry runs on `spacing/16` 64, `spacing/11` 44, `spacing/6` 24, `spacing/4` 16, `spacing/3` 12, `spacing/2` 8 and `spacing/1` 4. ## Guidelines - Do: Name the screen in the title and put the qualifier in the caption. One filled button for the one thing this screen exists to finish. - Avoid: Do not write a sentence into a block that is 132 wide once two actions are in. It truncates in the middle and the half that survives is the half nobody needed. Use large where the screen is a destination and the title is worth the room, compact where the user is passing through. Keep the back affordance unless there is nowhere to go back to: on Android hardware without a back key it is the only way out of a pushed screen. Decide the actions before the title: the title block is what they leave. --- --- title: Onboarding description: Ready onboarding screens built only from Appetite UI components. Welcome, sign in, permissions and the first run, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-onboarding --- # Onboarding Four onboarding screens, drawn light and dark, built from seven components that already exist in the system. Sixty instances and not one new component. ## Preview Four screens that take a new user from first launch to a signed in account. Two of them solve sign in twice, once social first and once email first, from the same components in a different order. Not every screen in this set is shown here. The Figma file carries all of them, in light and dark. Specimen: Onboarding, four screens, light and dark. 393 x 852 each, Platform ios-md. The set is drawn twice in Figma, a light row and a dark row, with Color Tokens set explicitly on every frame rather than inherited from the page. The dark screens are not a second drawing. They are the same instances resolved against the other mode. ## What it is built from Sixty instances across the four screens. Thirty six are Appetite UI components, nine are Lucide icons, fifteen are logo, brand mark and flag assets. Every one of them reads from the same variables, which is why a rebrand moves all four screens at once. | Component | Placed | Nested | Where | | --- | --- | --- | --- | | [Social Button](/docs/button) | 10 | 0 | Both Sign In screens and Sign Up | | [Button](/docs/button) | 8 | 0 | All four screens | | [Text Input](/docs/input) | 7 | 0 | Sign In and Sign Up | | [iOS / Status bar](/docs/ios) | 4 | 0 | All four screens | | [Logo](/docs/assets) | 4 | 0 | All four screens | | [Divider](/docs/divider) | 3 | 0 | Both Sign In screens and Sign Up | | [Pagination](/docs/pagination) | 1 | 3 | Welcome, carrying three dots | Placed counts an instance sitting in the screen. Nested counts an instance inside another instance, which is where the three Pagination dots and every glyph live. Lucide supplies mail, lock and eye-closed nine times. Assets supplies the Apple, Google, Facebook and browser marks plus one flag set, eleven times. ## Tokens Six collections behind the four screens. Switching one mode is the entire dark mode step. | Collection | Modes | What it drives here | | --- | --- | --- | | `Color Tokens` | Light, Dark | Every surface, label, border and the scrim | | `Foundations` | Default | Spacing, radius and control sizing | | `Typography` | Default | Headline and supporting text | | `Platform` | ios-md, ios-lg, android | Status bar and platform metrics, set to ios-md | | `Platform Bridge` | Brand, System | Available, not exercised by this set | | `Motion` | Default | Available, not exercised by a still export | The last two rows are listed because they are part of the same source, not because a flat screen uses them. Everything painted on these frames, including the local gradient over the photograph, resolves through Color Tokens. The photograph itself is the one unbound fill in the set, which is correct: a photograph is not a token. --- --- title: Settings description: Ready settings screens built only from Appetite UI components. Account, notifications, appearance and destructive actions, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-settings --- # Settings Three settings screens, drawn light and dark. Seventeen of the twenty nine placed instances are one component, which is why changing List changes the whole settings area. ## Preview Three screens for the part of an app nobody demos. A settings menu, a selection list carrying toggles, and an account form. Two of the three are almost entirely one component. Specimen: Settings, three screens, light and dark. 393 x 852 each, Platform ios-md. The set is drawn twice in Figma, a light row and a dark row, with Color Tokens set explicitly on every frame rather than inherited from the page. The dark screens are not a second drawing. They are the same instances resolved against the other mode. ## What it is built from Seventy seven instances across three screens. Thirty eight are Appetite UI components, thirty seven are Lucide icons, two are brand assets. Seven distinct components carry the whole set. | Component | Placed | Nested | Where | | --- | --- | --- | --- | | [List](/docs/list) | 17 | 0 | Menu and Selection | | [iOS / Status bar](/docs/ios) | 3 | 0 | All three screens | | [Top Bar](/docs/top-bars) | 3 | 0 | All three screens | | [Text Input](/docs/input) | 3 | 0 | Account Setup | | [Button](/docs/button) | 2 | 5 | All three, nested inside Top Bar | | [Toggle](/docs/toggle) | 0 | 4 | Selection, inside List rows | | [Avatar](/docs/avatar) | 1 | 0 | Account Setup | List carries seventeen of the twenty nine placed instances, and the twelve chevrons, four Toggles and every leading glyph sit inside it. That is the argument in one number: change List and the settings area changes, because the screen is a set of content decisions rather than a drawing. Lucide supplies twenty distinct icons thirty seven times. Assets supplies the Apple mark and one flag set. ## Tokens Six collections behind the three screens. Switching one mode is the entire dark mode step. | Collection | Modes | What it drives here | | --- | --- | --- | | `Color Tokens` | Light, Dark | Surfaces, labels, row separators, Toggle tracks | | `Foundations` | Default | Row height, group spacing, radius | | `Typography` | Default | Row labels, values and group headings | | `Platform` | ios-md, ios-lg, android | Status bar and platform metrics, set to ios-md | | `Platform Bridge` | Brand, System | Available, not exercised by this set | | `Motion` | Default | Available, not exercised by a still export | The last two rows are listed because they are part of the same source, not because a flat screen uses them. Worth checking if you are auditing your own file: even the hand drawn separators here have their stroke bound to a variable, so the whole set survives a mode switch without a single hardcoded colour. --- --- title: Empty State description: Ready empty, error and offline screens built only from Appetite UI components. The states most kits leave you to invent, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-empty-state --- # Empty State Two screens for the states most systems leave the designer to invent. Error and empty, drawn light and dark, from the same components as every other screen. ## Preview Two screens for the moments a product usually leaves blank. One thing went wrong, one thing has not happened yet, and both are built from the same components as every other screen in the system. Specimen: Empty State, two screens, light and dark. 393 x 852 each, Platform ios-md. The set is drawn twice in Figma, a light row and a dark row, with Color Tokens set explicitly on every frame rather than inherited from the page. The dark screens are not a second drawing. They are the same instances resolved against the other mode. ## What it is built from Nine instances across two screens. This is the smallest set in the library, and the small number is the argument: an empty state does not need new parts, it needs the same parts arranged with restraint. | Component | Placed | Nested | Where | | --- | --- | --- | --- | | [Button](/docs/button) | 4 | 0 | Both screens, one primary and one quiet | | [iOS / Status bar](/docs/ios) | 2 | 0 | Both screens | | [_Alert Status](/docs/alert) | 1 | 1 | Error only, carrying the x glyph | | [wallet-minimal](/docs/icons) | 1 | 0 | Add Money, sitting in a hand drawn badge | Add Money reports no nested instances at all, and that single anomaly is the tell. On Error the glyph is nested because it sits inside the _Alert Status component. On Add Money it sits at the top level because the badge around it was drawn by hand. The states section explains why, and what to do about it. ## States that ship Empty and error are the states most systems leave the designer to invent at the end of a project, under time pressure, one screen at a time. Here the badge that marks them is a component with four types. | Type | Glyph | Use it for | | --- | --- | --- | | `Error` | x | A request failed and the user can retry | | `Warning` | exclamation | Something needs attention before it fails | | `Success` | check | A task finished and the screen has nothing left to show | | `Verified` | check | An identity or account is confirmed | Four types, one axis, 64 x 64 in the component and scaled to 80 on these screens. Pick the type from what the user is meant to feel, not from the colour you want. Now the part that is drawn locally, because a docs page that hides it is worth nothing. On Add Money the badge is not an instance. It is a frame holding two ellipses at 70 and 60 with a wallet-minimal icon at 35, a hand made copy of the component sitting one screen away from the real thing. It happened for a reason worth fixing rather than scolding: the Type axis fixes the glyph per type, so there is no way to keep the badge and change the icon to a wallet. The fold back is specific. Give _Alert Status an instance swap property for its glyph, and this rebuild disappears along with every future one. Four rectangles named Divider, 345 x 179, also sit at the top and bottom of both screens doing the centring; they carry a real fill bound to `bg/white-0` rather than being empty spacers, so they are load bearing and should stay, but the name is wrong and should not survive the next pass. ## Tokens Six collections behind the two screens. Switching one mode is the entire dark mode step. | Collection | Modes | What it drives here | | --- | --- | --- | | `Color Tokens` | Light, Dark | Page surface, badge ring, badge fill, both Buttons | | `Foundations` | Default | The 345 content width and the block spacing | | `Typography` | Default | Headline and supporting line | | `Platform` | ios-md, ios-lg, android | Status bar and platform metrics, set to ios-md | | `Platform Bridge` | Brand, System | Available, not exercised by this set | | `Motion` | Default | Available, not exercised by a still export | The badge fill on Add Money resolves through `state/information/lighter-50`, which lands on a pale blue in Light and a saturated blue in Dark. Even the hand drawn badge is fully bound, so the local copy survives the mode switch. It is a component problem, not a token problem, and the two are worth telling apart when you audit your own file. --- --- title: Finance description: Ready finance screens built only from Appetite UI components. Balance, transactions, transfers and cards, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-finance --- # Finance Five finance screens, light and dark, assembled from the same components and the same variables as the rest of the system. Every control that has a component is an instance of it, so a rebrand moves all five at once. ## Preview Five finance screens assembled from the same components and the same variables as every other screen in the system. Retint the brand and all five move at once. Not every screen in this set is shown here. The Figma file carries all of them, in light and dark. Specimen: Finance, light and dark. 5 screens, 393 x 852, 131 instances per row. The Figma page holds every screen twice, a light row and a dark row. Each frame sets its modes explicitly, `Color Tokens = Light` or `Dark` and `Platform = ios-md`. Nothing is left to inherit, so a frame dropped into another file arrives in the theme it was drawn in. ## Components 131 instances per row, 262 across both. Every control that has a component is an instance of it, so a change to the component reaches all five screens without anyone opening them. That is 78 component instances. The other 53 are Lucide icons, 25 distinct names, every one an instance off the shared icon page rather than a pasted vector. No third party asset is placed on these screens. | Component | Per row | Where | | --- | --- | --- | | [Navigation](/docs/navigation) | 25 | Every screen, one bar and four labels each | | [Chip](/docs/chip) | 10 | History and Portfolio, five each | | [Button](/docs/button) | 8 | Dashboard, History, card, Pay | | [Status Bar](/docs/ios) | 5 | Every screen | | [Tab](/docs/tab) | 5 | History, one set and four selectors | | [Quick Action Button](/docs/button) | 4 | Dashboard | | [Top Bar](/docs/top-bars) | 4 | History, card, Portfolio, Pay | | [Section Header](/docs/top-bars) | 3 | Dashboard, card, Portfolio | | [List](/docs/list) | 3 | Card | | [Toggle](/docs/toggle) | 3 | Card, one per list row | | [Segmented Control](/docs/segmented-control) | 3 | Pay, one control and two selectors | | [Avatar](/docs/avatar) | 2 | Dashboard, Pay | | [Search](/docs/search-field) | 1 | Dashboard | | [Badge](/docs/badge) | 1 | Portfolio | | [Slider](/docs/slider) | 1 | Card, the spend limit | Drawn locally, not instances: the Portfolio line chart, 150 nodes, a stroked path with 147 hairline bars behind it, a dot and a tooltip; the Pay QR code, 336 vectors; seven 40 x 40 category tiles; and on the card screen two raw `LINE` nodes inside the settings list. Chart and QR have no component to be an instance of, which is the honest reason they are drawn. The two lines are not: Divider ships, and those two should fold back into it. ## Money Money screens fail in the same three places: numbers that do not line up, a total that changes weight between screens, and a colour that shouts on every row. Here is what the set actually does about it. | Element | How it is set | Colour | | --- | --- | --- | | Amount, debit | Right aligned, sm/emphasized, 14 Semi Bold | `text/strong-950` | | Amount, credit | Identical style, colour is the only change | `state/success/base-500` | | Date and time | Left aligned, xs/default, 12 Regular | `text/soft-500` | | Balance | 60 Semi Bold with the cents dropped to 36 | `static/static-white-0` | | Card number | lg/emphasized, 18 Semi Bold, four groups | `static/static-white-0` | | Category tile | 40 x 40 behind the transaction icon | `bg/weak-50` | Every amount is right aligned and every amount carries two decimals, so the decimal points stack down the column whatever the length. One emphasized style covers the whole list; a debit and a credit differ by colour and nothing else. Only the credit takes a colour, which is the decision worth copying: ordinary spending reads as ordinary, and the error red stays available for a real error. ## Tokens No screen carries a raw hex. Everything drawn on these five, including the chart, resolves through a variable, which is what makes the dark row a mode switch rather than a second design. | Token | Where it lands | Screens | | --- | --- | --- | | `text/strong-950` | Debit amounts, holding names, row titles | All five | | `text/soft-500` | Dates, times, tickers and secondary meta | All five | | `state/success/base-500` | Credit amounts, and nothing else | Dashboard, History | | `bg/weak-50` | The 40 x 40 tile behind each category icon | Dashboard, History, Portfolio | | `static/static-white-0` | Type set over the photo header and the card | Dashboard, card | | `brand/base` | The chart line, its dot and all 147 hairlines | Portfolio | | `bg/white-0` | The chart tooltip | Portfolio | Six collections carry the file: Foundations, Color Tokens, Typography, Motion, Platform and Platform Bridge. Color Tokens holds Light and Dark, Platform holds ios-md, ios-lg and android, Platform Bridge holds Brand and System. These screens pin Color Tokens and Platform on every frame and leave the rest at their defaults. --- --- title: Fitness description: Ready fitness screens built only from Appetite UI components. Activity, workouts, progress and goals, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-fitness --- # Fitness Six fitness screens, light and dark, assembled from the same components and the same variables as the rest of the system. The card photography ships as empty slots, each one addressable in a single place. ## Preview Six fitness screens assembled from the same components and the same variables as every other screen in the system. Retint the brand and all six move at once. Specimen: Fitness, light and dark. 6 screens, 393 x 852, 146 instances per row. The Figma page holds every screen twice, a light row and a dark row. Each frame sets its modes explicitly, `Color Tokens = Light` or `Dark` and `Platform = ios-md`. Nothing is left to inherit, so a frame dropped into another file arrives in the theme it was drawn in. ## Components 146 instances per row, 292 across both. Every control that has a component is an instance of it, so a change to the component reaches all six screens without anyone opening them. That is 84 component instances. Another 54 are Lucide icons, 25 distinct names, every one an instance off the shared icon page rather than a pasted vector. The last 8 are the photo slots, covered below. No third party asset is placed on these screens. | Component | Per row | Where | | --- | --- | --- | | [Navigation](/docs/navigation) | 25 | All but Exercise, one bar and four labels each | | [Button](/docs/button) | 12 | Every screen but Analytics, most of them icon only | | [Card](/docs/card) | 10 | Dashboard and Library, across icon, horizontal and vertical | | [Chip](/docs/chip) | 8 | Library five, Analytics three | | [Status Bar](/docs/ios) | 6 | Every screen | | [Section Header](/docs/top-bars) | 5 | Dashboard, Profile, Progress | | [Top Bar](/docs/top-bars) | 5 | All but Dashboard | | [Segmented Control](/docs/segmented-control) | 4 | Progress, one control and three selectors | | [Badge](/docs/badge) | 3 | Dashboard, Analytics, Progress | | [List](/docs/list) | 3 | Profile, the settings stack | | [Avatar](/docs/avatar) | 2 | Dashboard, Profile | | [Toggle](/docs/toggle) | 1 | Profile, on the notifications row | Drawn locally, not instances: two bar charts of seven bars each on Analytics and Progress, a calorie ring built from two ellipses, a weight rail with a fill and a dot, five achievement tiles, three personal record rows, and six divider shapes. Charts and the ring have no component to be an instance of. The dividers do: two `LINE` nodes in the Profile settings list, two more between the profile stats, and on Library and Exercise a rectangle named Divider carrying no fill at all. Those six should fold back into Divider, and the weight rail into Progress. ## Tokens No screen carries a raw hex. The charts, the ring and the rail all resolve through variables, which is what makes the dark row a mode switch rather than a second design. | Token | Where it lands | Screens | | --- | --- | --- | | `text/strong-950` | Stat values, record weights, row titles | All six | | `text/soft-500` | Units, dates and secondary meta | All six | | `text/sub-700` | Stat labels and settings row text | Analytics, Profile, Progress | | `brand/base` | Chart bars, the calorie ring and the rail fill | Analytics, Progress | | `bg/sub-200` | The unfilled half of every bar and the rail track | Analytics, Progress | | `border/soft-100` | The settings list edge and its two dividers | Profile | | `state/…/lighter-50` | Six different tints behind the small icon tiles | Profile, Progress | Geometry runs on the same file. Padding and gaps come from `dimensions/spacing`, corners from `dimensions/radius/lg`, `xl` and `2xl`. Six collections carry the file: Foundations, Color Tokens, Typography, Motion, Platform and Platform Bridge. Color Tokens holds Light and Dark, Platform holds ios-md, ios-lg and android, Platform Bridge holds Brand and System. --- --- title: Social description: Ready social screens built only from Appetite UI components. Feed, profile, messages and composing, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-social --- # Social Eight social screens, light and dark, the densest set in the library. 197 instances a row off seven component sets, and two screens that draw nothing by hand at all. ## Preview Eight social screens assembled from the same components and the same variables as every other screen in the system. Retint the brand and all eight move at once. Not every screen in this set is shown here. The Figma file carries all of them, in light and dark. Specimen: Social, light and dark. 8 screens, 393 x 852, 197 instances per row. The Figma page holds every screen twice, a light row and a dark row. Each frame sets its modes explicitly, `Color Tokens = Light` or `Dark` and `Platform = ios-md`. Nothing is left to inherit, so a frame dropped into another file arrives in the theme it was drawn in. ## Components 197 instances per row, 394 across both, the densest set in the library. Every control that has a component is an instance of it, so a change to the component reaches all eight screens without anyone opening them. That is 137 component instances off seven sets. The other 60 are Lucide icons, 18 distinct names, every one an instance off the shared icon page rather than a pasted vector. No third party asset is placed on these screens. | Component | Per row | Where | | --- | --- | --- | | [Avatar](/docs/avatar) | 50 | Every screen but Explore, plus six presence dots on Chat | | [Button](/docs/button) | 37 | Every screen but Explore, nine each on Search and Followers | | [Navigation](/docs/navigation) | 30 | Six screens, one bar and four labels each | | [Status Bar](/docs/ios) | 8 | Every screen | | [Top Bar](/docs/top-bars) | 4 | Home, both Chat screens, Followers | | [Search](/docs/search-field) | 4 | Explore, Search, Chat list, Followers | | [Chip](/docs/chip) | 4 | Explore | Drawn locally, not instances: sixteen story rings, five message bubbles and a reply field on the thread, another reply field and a five segment progress bar on Story, and thirty photo rectangles. The photos are content and belong there. The rings and the progress bar do not: the ring is one shape repeated sixteen times a row and wants to be a component, and the segment bar should fold back into Progress. Search and Followers draw nothing at all, 31 and 34 instances each and not one loose shape. ## Tokens No screen carries a raw hex. Bubbles, overlays and hairlines all resolve through variables, which is what makes the dark row a mode switch rather than a second design. | Token | Where it lands | Screens | | --- | --- | --- | | `text/strong-950` | Display names, captions, stat values | Six screens | | `text/soft-500` | Handles, timestamps, counts | Six screens | | `bg/weak-50` | Received message bubbles | Chat thread | | `state/information/base-500` | Sent message bubbles | Chat thread | | `static/static-white-0` | Sent bubble text and every story overlay | Chat thread, Story | | `border/soft-100` | Row and header hairlines | Home, Chat | | `colors/alpha/black/alpha-24` | The unplayed track behind the story segments | Story | Geometry runs on the same file. Padding and gaps come from `dimensions/spacing`, every ring and pill from `dimensions/radius/full`, the bubble from `dimensions/radius/2xl` with its tail corner dropped to `sm`. Six collections carry the file: Foundations, Color Tokens, Typography, Motion, Platform and Platform Bridge. Color Tokens holds Light and Dark, Platform holds ios-md, ios-lg and android, Platform Bridge holds Brand and System. --- --- title: AI screens description: Ready AI screens built only from Appetite UI components. Prompt entry, streaming replies, citations and the states a model forces you to handle. group: App screens source: https://www.appetiteui.com/docs/screens-ai --- # AI screens Eleven screens covering a whole assistant: an empty prompt, a streaming answer, cited sources, an image result, and the states a model forces you to handle. Every control is an instance of a component you already have, so one token change moves all eleven. ## Preview Eleven screens covering a whole assistant: an empty prompt, a streaming answer, cited sources, an image result, and the states a model forces you to handle. Each screen exists twice in the file, once with Color Tokens on Light and once on Dark, so the theme button swaps an export rather than restyling anything. Specimen: AI screens, 11 screens. 393 x 852 each, Light and Dark. The Light row and the Dark row are the same eleven frames with one variable mode changed, not two separate builds. ## What it is built from This is the whole point of the set. The eleven screens place 128 instances, 83 of them Appetite UI components across 9 distinct components and 45 of them Lucide icons. Two components, Message and Prompt Composer, carry the assistant itself. Four of the eleven screens are another screen with something laid over it. Each overlay is an instance sitting on a local scrim, and each brings its own status bar, which is why those screens count two. | Component | Placed | Where it appears | | --- | --- | --- | | [Button](/docs/button) | 21 | Every screen except Projects and Instructions | | [AI / Message](/docs/ai-component) | 20 | Thread, Generating, Image, Attachment, Error, History, Manage chat | | [iOS / Status bar](/docs/ios) | 15 | Every screen, twice on the four with an overlay | | [AI / Prompt Composer](/docs/ai-component) | 10 | Every screen except Instructions | | [Chip](/docs/chip) | 10 | New chat, Tools, Projects, Instructions | | [Top Bar](/docs/top-bars) | 2 | Projects, Instructions | | [Context Menu](/docs/context-menu) | 2 | Tools, Manage chat | | [Navigation Drawer](/docs/drawer) | 2 | History, Manage chat | | [Action Sheet](/docs/action-sheet) | 1 | Instructions | | [Lucide icons](/docs/icons) | 45 | 12 distinct icons, one stroke weight | | Overlay | Screens | What it opens onto | | --- | --- | --- | | [Context Menu](/docs/context-menu) | Tools, Manage chat | New chat, and the thread with the drawer already open | | [Navigation Drawer](/docs/drawer) | History, Manage chat | The thread, pushed behind a scrim | | [Action Sheet](/docs/action-sheet) | Instructions | The project screen | Everything else is layout and text. This set draws more locally than the other two: the model switcher under the header, the icon tile on New chat and Projects, the project and file rows, the source cards and the figure table inside a reply. The switcher and the rows duplicate Dropdown and List, and are the two worth folding back in. Manage chat is the deepest screen in the set: a thread, a drawer over it and a menu over that, 16 instances placed and 43 nested. It is the one to look at when you want to know whether the layering holds. ## Tokens No screen carries a raw colour, a raw radius or a raw type size. Every frame sits in a variable mode and reads down through the same six collections as every component. | Collection | Modes | What the screens take from it | | --- | --- | --- | | Foundations | Default | The primitive ramps every other collection points at | | Color Tokens | Light, Dark | Every surface, text and border colour on the screens | | Platform | ios-md, ios-lg, android | Frame width, radius and inset. The screens sit on ios-md | | Typography | Default | The type ramp behind every label on every screen | | Platform Bridge | Brand, System | Apple and Material roles, unused here but reachable | | Motion | Default | Durations, curves and springs for the transitions between screens | Every AI frame is pinned to Color Tokens Light or Dark and Platform ios-md. The dark row is the light row with one mode changed, which is why the two exports agree line for line. Rebrand at Colors and all eleven screens follow. --- --- title: Booking description: Ready booking screens built only from Appetite UI components. Search, stay detail, dates and checkout, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-booking --- # Booking Eleven screens, a whole stay booking flow from search to a confirmed trip. Nothing here is drawn for the mock-up: every control is an instance of a component you already have, so one token change moves all eleven. ## Preview Eleven screens, a whole stay booking flow from search to a confirmed trip. Each screen exists twice in the file, once with Color Tokens on Light and once on Dark, so the theme button swaps an export rather than restyling anything. Specimen: Booking, 11 screens. 393 x 852 each, Light and Dark. The Light row and the Dark row are the same eleven frames with one variable mode changed, not two separate builds. ## What it is built from This is the whole point of the set. The flow places 100 instances, 85 of them Appetite UI components across 13 distinct components and 15 of them Lucide icons. Edit Button once and it changes on nine of the eleven screens. | Component | Placed | Where it appears | | --- | --- | --- | | [Button](/docs/button) | 21 | Results, Listing, Filters, Dates, Guests, Review and pay, Confirmed, Trips empty, Profile | | [Chip](/docs/chip) | 15 | Explore, Results, Filters, Dates | | [iOS / Status bar](/docs/ios) | 11 | Every screen | | [Top Bar](/docs/top-bars) | 8 | Every screen that carries a title bar | | [List](/docs/list) | 8 | Filters, Review and pay, Profile | | [Horizontal Card](/docs/card) | 8 | Results, Trips | | [Navigation](/docs/navigation) | 5 | Explore, Results, Trips, Trips empty, Profile | | [Vertical Card](/docs/card) | 2 | Explore | | [Segmented Control](/docs/segmented-control) | 2 | Dates, Trips | | [Avatar](/docs/avatar) | 2 | Listing, Profile | | [Date Picker](/docs/picker) | 1 | Dates | | [Range Slider](/docs/slider) | 1 | Filters | | [Badge](/docs/badge) | 1 | Listing | | [Lucide icons](/docs/icons) | 15 | 14 distinct icons, one stroke weight | Everything else on these screens is layout and text. Six things are drawn locally rather than instanced: the round overlay button on Listing, the search pill on Explore, the inline rating, the amenity rows, the stepper wrappers, whose plus and minus are Button instances, and the price histogram behind the Range Slider. The first two duplicate something the system already ships, Button and Search, and are the two worth folding back in. ## Tokens No screen carries a raw colour, a raw radius or a raw type size. Every frame sits in a variable mode and reads down through the same six collections as every component. | Collection | Modes | What the screens take from it | | --- | --- | --- | | Foundations | Default | The primitive ramps every other collection points at | | Color Tokens | Light, Dark | Every surface, text and border colour on the screens | | Platform | ios-md, ios-lg, android | Frame width, radius and inset. The screens sit on ios-md | | Typography | Default | The type ramp behind every label on every screen | | Platform Bridge | Brand, System | Apple and Material roles, unused here but reachable | | Motion | Default | Durations, curves and springs for the transitions between screens | Every Booking frame is pinned to Color Tokens Light or Dark and Platform ios-md. The dark row is the light row with one mode changed, which is why the two exports agree line for line. Rebrand at Colors and all eleven screens follow. --- --- title: Foodie description: Ready food ordering screens built only from Appetite UI components. Browse, dish detail, basket and tracking, in light and dark. group: App screens source: https://www.appetiteui.com/docs/screens-foodie --- # Foodie Eleven screens, an ordering flow from a browse list to a delivery you can watch arrive. Nothing here is drawn for the mock-up: every control is an instance of a component you already have, so one token change moves all eleven. ## Preview Eleven screens, an ordering flow from a browse list to a delivery you can watch arrive. Each screen exists twice in the file, once with Color Tokens on Light and once on Dark, so the theme button swaps an export rather than restyling anything. Specimen: Foodie, 11 screens. 393 x 852 each, Light and Dark. The Light row and the Dark row are the same eleven frames with one variable mode changed, not two separate builds. ## What it is built from This is the whole point of the set. The flow places 106 instances, 84 of them Appetite UI components across 9 distinct components and 22 of them Lucide icons. Nine components carry an entire ordering app. | Component | Placed | Where it appears | | --- | --- | --- | | [Button](/docs/button) | 18 | Restaurant, Dish, Cart, Cart empty, Checkout, Tracking, Favourites, Account | | [Horizontal Card](/docs/card) | 16 | Home, Category, Orders | | [List](/docs/list) | 16 | Dish, Cart, Checkout, Account | | [iOS / Status bar](/docs/ios) | 11 | Every screen | | [Top Bar](/docs/top-bars) | 8 | Every screen that carries a title bar | | [Chip](/docs/chip) | 8 | Home, Category | | [Navigation](/docs/navigation) | 4 | Home, Category, Orders, Account | | [Avatar](/docs/avatar) | 2 | Tracking, Account | | [Segmented Control](/docs/segmented-control) | 1 | Orders | | [Lucide icons](/docs/icons) | 22 | 12 distinct icons, one stroke weight | Everything else is layout and text. Six things are drawn locally rather than instanced: the round overlay button on Restaurant and Dish, the search pill on Home, the inline rating, the dish and basket rows, the quantity steppers, whose plus and minus are Button instances, and the four step delivery timeline on Tracking. The overlay button, the search pill and the timeline duplicate Button, Search and Progress Steps, and are the three worth folding back in. ## Tokens No screen carries a raw colour, a raw radius or a raw type size. Every frame sits in a variable mode and reads down through the same six collections as every component. | Collection | Modes | What the screens take from it | | --- | --- | --- | | Foundations | Default | The primitive ramps every other collection points at | | Color Tokens | Light, Dark | Every surface, text and border colour on the screens | | Platform | ios-md, ios-lg, android | Frame width, radius and inset. The screens sit on ios-md | | Typography | Default | The type ramp behind every label on every screen | | Platform Bridge | Brand, System | Apple and Material roles, unused here but reachable | | Motion | Default | Durations, curves and springs for the transitions between screens | Every Foodie frame is pinned to Color Tokens Light or Dark and Platform ios-md. The dark row is the light row with one mode changed, which is why the two exports agree line for line. Rebrand at Colors and all eleven screens follow. --- --- title: iOS description: Apple's own patterns rebuilt as Figma components, from the status bar to the keyboard, carrying your brand or the untouched system values. group: Platforms source: https://www.appetiteui.com/docs/ios --- # iOS Apple's own patterns, rebuilt as Figma components. Every one reads from the same tokens as the rest of the system, so a screen can carry your brand or return to the untouched platform values without being redrawn. ![The iOS mirror in Appetite UI](/images/au_ios_platform.webp) This is not a replacement for the Apple Design Resources. It is the layer that puts your brand inside them. Each pattern is rebuilt on Appetite UI tokens. The structure stays Apple: the same anatomy, the same spacing, the same behaviour a user already knows. Colour, type, radius and elevation come from your system, so a Toolbar or a Sheet reads as native and reads as yours at the same time. The Platform Bridge holds both readings at once. Brand mode resolves every Apple role to an Appetite UI token. System mode resolves the same roles back to Apple's own values, untouched. One swap on one collection, and there is no second set of screens to keep in sync. ## Brand and system Nothing in the Apple mirror is filled with a hex. Platform Bridge holds 52 iOS roles and every one resolves twice. Set the mode on a frame and the whole screen re-resolves: geometry and structure hold, only the values move. Figure: the Platform Bridge chain, step 2. iOS / Segmented Control, Size Large, is 370 × 50 with padding 2, gap 4, segment 46, in both modes. Its track reads iOS/tertiarySystemFill: flat grey in Brand, translucent in System. ## What is covered Twenty-one pattern pages in the Apple mirror, one row each. Counted in the Figma file, not estimated. | Pattern page | Sets | Components | | --- | --- | --- | | Status Bar | 1 | 2 | | Tab Bar | 3 | 20 | | Toolbar | 1 | 4 | | Text Field | 1 | 4 | | List | 2 | 15 | | Controls | 6 | 33 | | Menu | 3 | 19 | | Page Control | 2 | 5 | | Action Sheet | 1 | 4 | | Activity View | 2 | 28 | | Alert | 3 | 11 | | Button | 7 | 114 | | Dynamic Island | 3 | 15 | | Face ID | 1 | 3 | | Keyboard | 12 | 59 | | Live Activity | 1 | 6 | | Notification | 1 | 6 | | Picker | 3 | 11 | | Sheet | 2 | 10 | | Toggle | 1 | 8 | | Widget | 1 | 6 | | Total | 57 | 383 | Sets are variant sets. Components counts every variant inside them. Button and Keyboard carry the largest matrices because both cross size with state. Counted 8 Sep 2026. ## Tokens and metrics Device geometry lives in its own collection, one mode per device class. The iOS half of Platform Bridge is 52 roles. | Variable | ios-md | ios-lg | | --- | --- | --- | | `grid/safe-area/top` | 59 | 62 | | `grid/safe-area/bottom` | 34 | 34 | | `grid/columns` | 16 | 16 | | `platform/min-touch-target` | 44 | 44 | | `platform/bar/top-height` | 44 | 44 | | `platform/bar/bottom-height` | 49 | 49 | | `platform/list-row/min-height` | 44 | 44 | | `platform/control/corner` | 8 | 8 | | `platform/card/corner` | 12 | 12 | | `platform/sheet/corner` | 12 | 12 | | `platform/dialog/corner` | 12 | 12 | | `platform/switch/track-width` | 51 | 51 | | `platform/switch/track-height` | 31 | 31 | | `platform/screen-padding` | 16 | 16 | The two iOS modes differ in one variable: `grid/safe-area/top`, 59 on ios-md and 62 on ios-lg. A third mode, android, sits in the same collection. Apple publishes the 44pt target and 11pt minimum text; every other number is an Appetite UI decision. Both font variables read Inter. | Role | Brand | System | | --- | --- | --- | | `iOS/tintColor` | #155DFC | #007AFF | | `iOS/label` | #0E0E0F | #000000 | | `iOS/secondaryLabel` | #48484A | #3C3C43 at 60% | | `iOS/separator` | #E9E9EA | #3C3C43 at 18% | | `iOS/systemGreen` | #22C55E | #34C759 | | Shape and type | Brand | System | | --- | --- | --- | | `iOS/sys/shape/corner-small` | 8 | 10 | | `iOS/sys/shape/corner-large` | 24 | 26 | | `iOS/sys/typography/body` | 18 | 17 | | `iOS/sys/typography/subheadline` | 14 | 15 | | `iOS/sys/typography/footnote` | 12 | 13 | A sample of the 52 roles, light. Each resolves again for dark: `iOS/tintColor` becomes #2B7FFF in Brand and #0A84FF in System. Brand values point into your Appetite UI color tokens, so one brand change moves them all. iOS, SwiftUI, SF Pro and SF Symbols are trademarks of Apple Inc, Material Design of Google LLC. Appetite UI is not affiliated with, endorsed by or sponsored by either; platform names describe compatibility. No Apple artwork, symbol or typeface is included. Every layer is Appetite UI's own geometry on its own tokens, in Inter with Lucide icons. --- --- title: Material 3 description: Material 3 patterns rebuilt as Figma components, from the app bar to the snackbar, carrying your brand or the untouched system values. group: Platforms source: https://www.appetiteui.com/docs/material-3 --- # Material 3 Material's own patterns, rebuilt as Figma components. Every layer reads a token, so one screen carries your brand or drops back to the untouched system values without a single detach. ![The Material 3 mirror in Appetite UI](/images/au_m3_platform.webp) This is not a replacement for the Material 3 kit. It is the layer that puts your brand inside it. Each pattern is rebuilt on Appetite UI tokens. The structure stays Material: the same anatomy, the same spacing, the same behaviour a user already knows. Colour, type, shape and elevation come from your system, so an App Bar or a Dialog reads as Material and reads as yours at the same time. The Platform Bridge holds both readings at once. Brand mode resolves every Material role to an Appetite UI token. System mode resolves them back to Material's own baseline, untouched. One swap on one collection, and there is no second set of screens to keep in sync. ## Brand and system Platform Bridge is one collection with two modes. Every Material role in it is an alias, never a literal, so switching the mode on a frame moves the whole mirror at once. Figure: the Platform Bridge chain, step 3. Geometry owned by the platform holds either way: the android switch track is 52 by 32, list rows 56, the touch target 48. Nothing is repainted and nothing is detached. ## What is covered Seventeen pattern pages in the Material mirror, one row each. Counted in the Figma file, not estimated. | Pattern page | Sets | Components | | --- | --- | --- | | Status Bar | 0 | 1 | | App Bar | 1 | 4 | | Switch | 1 | 4 | | Checkbox | 1 | 6 | | Radio Button | 1 | 4 | | Card | 1 | 3 | | Chip | 1 | 8 | | Text Field | 1 | 8 | | List | 1 | 6 | | Navigation Bar | 2 | 7 | | Button | 8 | 270 | | Dialog | 6 | 20 | | Picker | 12 | 93 | | Sheet | 4 | 12 | | Snackbar | 2 | 14 | | Tooltip | 0 | 2 | | Keyboard | 1 | 8 | | Total | 43 | 470 | Sets are variant sets. Components counts every variant inside them. Button and Picker hold most of the total because both cross size with state. Status Bar and Tooltip carry no set: each is a single component, not a matrix. Counted 8 Sep 2026. ## Tokens and state layers Every Material role in Platform Bridge is prefixed M3/sys/: 32 colour roles, 6 corner steps, 15 typescale steps, 7 state opacities. Resolved in Light. | Role | Brand | System | | --- | --- | --- | | `color/primary` | #155DFC | #6750A4 | | `color/secondary-container` | #DBEAFE | #E8DEF8 | | `color/error` | #FB2C36 | #B3261E | | `color/surface` | #FFFFFF | #FEF7FF | | `color/on-surface` | #0E0E0F | #1D1B20 | | `color/on-surface-variant` | #48484A | #49454F | | `color/outline` | #8E8E93 | #79747E | | `color/surface-container-highest` | #DEDEE0 | #E6E0E9 | | Shape role | Brand | System | | --- | --- | --- | | `corner-extra-small` | 4 | 4 | | `corner-small` | 8 | 8 | | `corner-medium` | 12 | 12 | | `corner-large` | 18 | 16 | | `corner-extra-large` | 24 | 28 | | `corner-full` | 9999 | 9999 | Brand is not only a recolour. Two of the six corner steps move: Brand routes them through `dimensions/radius/*`, System holds Material's own. Typescale binds the same way: `typescale/body-large` is `typography/fontSize/base` in Brand and 16 in System. | State opacity | Both modes | Covers | | --- | --- | --- | | `state/hover-opacity` | 8 | Pointer over a target | | `state/focus-opacity` | 10 | Keyboard focus | | `state/pressed-opacity` | 10 | Touch down | | `state/disabled-container-opacity` | 12 | A disabled container | | `state/dragged-opacity` | 16 | An item being moved | | `state/scrim-opacity` | 32 | The wash behind a dialog | | `state/disabled-content-opacity` | 38 | A disabled label or icon | The seven opacities are the one part of the mirror the Bridge does not touch: bare numbers, the same in both modes, because a state layer is a rule about interaction, not a brand decision. They bind straight to node opacity on `_M3 / State layer` 10166:478, over a fill of `M3/sys/color/on-surface`. Material Design is a trademark of Google LLC. iOS, SwiftUI, SF Pro and SF Symbols are trademarks of Apple Inc. Appetite UI is not affiliated with, endorsed by or sponsored by either; platform names here describe compatibility. These patterns are referenced under CC BY 4.0 and redrawn in Appetite UI's own geometry, on its own tokens, in Inter, with Lucide icons. Material Symbols are not bundled: add them yourself and they fall under the Apache License 2.0.