The mark
The WafR “W” is not an image file. It is drawn as three vector strokes
in code (SmoothLogoPainter) so it can be recoloured,
re-timed and re-used as the app’s loading indicator. The strokes rise
one after another, hold, then drop away — a 2.8 second loop.
The mark is always a single flat colour — brand purple on light surfaces, white on the mesh gradient or any dark surface. Never multi-colour, never outlined.
Scale proportionally. The glass loading box renders it at 50 % scale inside a 20 px-radius blurred card.
WalyMainLoader. A rainbow-cycling variant
(WalyColoredLoading) shifts hue every 4 s during
long waits.
Brand palette
Purple is the identity; sky blue is the accent that keeps the gradients from going flat. The two deep “space” purples only ever appear inside the animated mesh background, never as a surface colour.
Mesh & button gradients
Three gradients carry nearly all the brand expression. They are diagonal (top-left → bottom-right) and they change between light and dark mode.
Type scale
System typeface on each platform (San Francisco on iOS, Roboto on Android) — no custom font is shipped. Primary text carries the weight; secondary text is reserved for supporting copy and is a lighter grey in both themes.
The spacing scale
A loose 8-point grid. m (18) is the workhorse: it is the
padding inside every card, every input field and every standard box.
Compact variant for list chips: 18 px horizontal × 12 px vertical.
Before a section header inside a list it opens up to 24 px.
Deliberately tight: cards carry their own 18 px padding, so the content still breathes.
Safe area + 100 px when a bottom navigation bar is present. Keyboard insets are added on top automatically.
Used inside buttons, chips and rows so icon-and-label reads as one unit.
Keeps summary cards in a row visually even when their content lengths differ.
Corner radius
Soft, never sharp. 18 px is the default for cards and buttons; full pills are used for anything selectable or countable.
Depth & shadow
Light mode gets a soft, two-layer shadow for depth. Dark mode gets no shadow at all — surfaces separate by brightness instead, which keeps dark screens clean rather than muddy. Toggle the theme to see the difference.
0 2px 6px @ 6 % black, plus 0 1px 2px @ 3 %. Radius 18.
Shadow switched off. Used for nested cards inside an already-elevated surface.
A tinted purple shadow (light) or lilac (dark) that compresses as the button is pushed down.
Blur & glass
Blur is how the apps create hierarchy without adding more colour. It appears in three distinct roles: as a modal scrim, as a floating glass surface, and as a transition effect on content appearing.
The background blurs and darkens progressively as the sheet animates in over 380 ms — both values are tied to the animation, so it never pops. Dragging the sheet back down lightens the scrim again.
A translucent tint over a blurred backdrop, clipped to a 20 px radius. Used for the loading box, the floating back/title bar over the map picker, the suggestions overlay and the receipt preview.
blur 8 → 0 · 500 ms
blur 6 → 0 · 600 ms
blur 0 → 8 · 380 ms
Content never simply “appears”. It resolves out of a soft blur while fading and scaling up slightly — that combination is the app’s signature feel.
Component language
The recurring building blocks. Everything sits on a card, every card uses the same radius and padding.
Pill-shaped. Unselected: 10 % tint of its status colour with the icon showing. Selected: solid fill, white label, icon animates away and an optional count appears. 200 ms easeOutCubic. Tap one.
Always a full pill, minimum 20 px wide so single digits stay circular.
Filled, radius 16, 18 px padding. Border thickens to 1.8 px and turns brand purple on focus; red on error.
Label left in secondary text, value right in semibold primary. Hairline divider between rows, none after the last.
Shimmer boxes mirror the shape of the content that is coming — never a spinner in a list. 2 s pulse, radius 8, then each one cross-fades into its real content.
The blocking loader: animated mark inside a frosted 20 px card, floating over whatever was on screen.
Sheets, drawer, date picker & switcher
These are not mock-ups. The phone below runs the real Flutter package
compiled to the web — the same AppBottomSheet,
AppDrawer, WalyDateRangePicker and
WalySegmentedSwitcher that ship in the apps. Tap, drag
and dismiss them exactly as a user would; the theme toggle in its top
bar switches the whole app between light and dark.
Run this once from docs/design-system/ — it compiles
example/ into live/:
npm run build:preview
The bottom sheet, the multi-step sheet and both drawers all run through the same drag-to-dismiss shell. They differ only in which edge they are anchored to — so they animate, blur and dismiss identically.
Sheets keep a small side margin and float above the safe area; the drawer is capped so a slice of the page stays visible behind it. Corners are 24 px, the scrim is blur 8 over black at 38 %.
Drag a sheet downward or a drawer back toward its edge: the panel follows your pointer and the scrim lightens as it goes. Release early and it springs back. Try it in the frame.
The drawer's items don't ride in with the panel — they resolve out of blur one by one once it is almost in place, so the drawer feels filled rather than flown in.
Each step pops its own response and the sheet decides where to go next. The card's height animates between steps and a dot indicator stretches the active dot into a pill.
A range picker, not a single-date picker. Swipe left/right to page months — the outgoing grid scales away toward the swipe and the incoming one grows in from the other side. Tapping the month or year opens a month/year grid with the same transition.
One pill with a sliding brand-purple thumb; icons and labels recolour to white as it arrives under them. The same widget covers tab switches, filter switches and sort-direction toggles.
Icon library
A small, domain-specific SVG set that sits alongside the standard
Material icons. All icons are single-path, solid-filled and drawn on a
24 × 24 grid — which means they can be tinted to any colour at
runtime. These are the real files from assets/icons/.
Default icon size is 24 px (20 px inside buttons, 14 px inside chips). Default tint is near-black in light mode and secondary grey in dark mode; brand purple signals an interactive or active icon.
Lottie library
Illustrated animations carry every empty, error, success and
permission moment — so the apps never show a bare error string. These
are the live files from assets/animations/.
How things move
Motion is quick, soft and physical. Nothing snaps, nothing bounces excessively, and nothing moves for longer than ~700 ms. Easing is almost always easeOutCubic: fast start, gentle landing.
Content reveal
A block of content resolving out of blur as it fades and scales up from 92 %.
Staggered list
List items arrive one after another, each from blur + 94 % scale.
Fade & slide up
The alternative entrance: rises 30 % of its height into place.
Press feedback
Everything tappable compresses under the finger, its shadow deepens, and a haptic fires.
Shimmer
Loading placeholders and the CTA both sweep a soft highlight across themselves.
Bottom sheet
Slides up while the backdrop blurs and darkens in step. Drag it down to dismiss — the scrim lightens as you pull.
A light tactile click the moment the finger lands.
A confirmation tap when the action actually fires.
A light pulse to confirm the gesture was recognised.