At Bell — design system

The single reference for the prototype and for the Flutter build that follows. Every value here is taken from the live app (at_bell_colors.dart, at_bell_tokens.dart, at_bell_typography.dart) — nothing was invented. Where the prototype needed something the app doesn't have yet, it's marked NEW and belongs in the Flutter theme too.

01Colour

Brand palette owner-confirmed 2026-06-13. The semantic colours below are derived from the brand — no new hex was introduced, which is why success is mint and warning is peach rather than the usual green/amber.

Neutrals

Tinted surfaces

Semantic — derived, not new

02Type

Roboto for Latin, Tajawal for Arabic — both bundled in the app, so text renders correctly offline. Weights in use: 300, 400, 500. Bold (700) is reserved and rarely used.

Scale

Hero · 32/40 · 300

Heading 1 · 24/32 · 300

Heading 2 · 20/28 · 400

Heading 3 · 18/24 · 500

Body large · 16/24 · 400

Body · 14/20 · 400 — the default for everything

Caption · 12/16 · 400 — secondary information

Micro · 11/15 · 400 — notes and hints only

03Space & radius

Spacing scale

Screen padding is 18px. Minimum touch target 44px.

Radius

04Buttons

One primary action per screen. The orange pill carries a soft red shadow — that shadow is part of the brand, not decoration.

Primary

Dark

Ghost

Disabled

Loading

Small

Text

05Fields

We’ll send a code on WhatsApp.

That doesn’t look like a Kuwait mobile number.

Search

In the prototype nothing validates — every field accepts anything and moves on. The error styling above exists so the real build has it specified.

06Chips & selectors

Chips state a fact, never a failure. “Pickup only” is information, not an error — this is the single most important tone rule in the product.

✓ enrolled · Grade 3-A pickup only from your booking 1 due Mains

Selector — the "who is this for" control

✓ enrolled · Grade 3-A

Tabs — the niche switcher

🍱 Food✏️ Stationery 👕 Uniforms📚 Library

07Cards, rows & products

List row

Option (radio)

Class deliveryYou have no class here

Product

Kid meal box Small portion · balanced 1.250 KD +
Cookie pack Three, baked daily sold out today ·

08Banners, toast & skeleton

Toast

Kid meal box added

Skeleton

09Screen states

Every list screen must specify all of these. In the prototype they're switchable from the side panel so they can be reviewed without faking data.

StateWhenWhat the user sees
loadingFetchingSkeleton rows in the shape of the real content
emptyNothing published“Nothing on today’s list” — never a blank screen
no-resultsSearch found nothingSuggest a wider area, offer “order to my address”
sold-outItem unavailableRow dimmed, chip “sold out today”, add button inert
cut-offPast the order deadlineWarn banner + list stays usable, now for tomorrow
closedPlace not serving“Closed today” + a route out (order to my address)
errorRequest failed“On our side, not yours” + Try again
offlineNo connectionExplains it will load itself when back online
not-enrolledNo room at this placeChip “pickup only” — shown up-front, never at checkout
payment-failedCard declined“Nothing was charged” stated explicitly

10Catalogue model & who creates what

Written down before any coding, because it decides both the app and the admin panel. The one hard line: a module is behaviour, not a label.

PLACE        Sunrise International School        ← super-admin creates
 └─ MODULE   Food · School shop · Library ·      ← switched ON, never created
             Plans · Bills                          (each one is code)
     └─ CATEGORY  Mains · Light · Drinks          ← the supplier creates freely
                  Stationery · Uniforms · Bags
         └─ ITEM      Daily shirt — white         ← the supplier adds
             └─ VARIANT  Size 8-9y                ← an option, not a category
LevelWhoNotes
PlaceSuper-adminCreates the school/office, switches modules on
Module → supplierSuper-adminFood → the canteen · Shop → a supplier · Library → the school
Categories + itemsThat supplier onlyThe canteen never sees the uniform catalogue
Three decisions worth remembering:
· Stationery and Uniforms are categories, not modules. Same act — buy a thing, one basket. Only Library is separate, because you borrow and it comes back.
· No sub-categories. One level covers every catalogue we have; two doubles the admin work and buys nothing until a real catalogue demands it.
· Size is a variant, not a category — otherwise "Shirt 6-7y" and "Shirt 8-9y" become separate products and the catalogue becomes unmanageable.

Admin panel

One nav item — Catalogue — with a module switcher at the top of the page, rather than one nav item per module. The nav then stops growing every time a module is added, and a canteen that only sells food never sees pages it can't use. For the super-admin the same catalogue also appears nested under the vendor (Vendors → Final Kitchen → Catalogue), since a global list would beg the question "whose?".

11Product rules the UI must obey

#RuleHow it shows up
1No roles, anywhereSign-up asks name + phone only. No “I am a…” step exists.
2Location decides, not identity“Where will you eat?” is the first question after sign-up.
3A place limits supply, never permissionA stranger sees the whole menu; only delivery differs.
4No room → pickup onlyA fact chip on the catalogue, before anything is added.
5Kid eater → kid list, kid priceMenu re-filters the instant the eater changes.
6Plans → only with a room hereBlocks a plan, never a meal. Day-by-day always works.
7Self-declare — no verifier“Add a person” saves immediately. Nobody approves anything.
8The list is never genericAlways scoped to one person, switchable in one tap.
9Bills are pushed, never addedOwn section. Never beside a “+” button.
10Facts, not errorsChips state what is true. Red is for genuine failures only.
Keeping this honest: if a screen needs a value that isn't in css/tokens.css, add it there first. The moment a raw hex appears in a screen, this file stops being a spec and becomes decoration.