The behaviour rules, valid in M3 and in the retro variants. Each one comes from a real screen in the repos.
Hierarchy and actions
- One primary action per screen, where the thumb or the eye expects it: FAB bottom right on mobile ("Meal" in Stabilité),
btn-filledbutton to the right of the page title on the web. - Urgency gets its own colour, not its own size. The "Crash SOS" FAB is
ec/on-ecnext to the "Meal" FAB inpc: same size, different colour. Never enlarge a button to draw attention. - Dialog button order: secondary action (text) on the left, primary action on the right. The destructive action is
btn-danger, neverbtn-filled. - No useless blank space in a card. A short element (chip, status, action, value) never sits alone on its line if it fits next to the title or another element: status and action in the header, metadata grouped on one line ("Port 443 · 3 d"). A card takes the height of its content, with no stretched empty area.
- A disabled button says why: "Pick at least one symptom" replaces the label while the condition is unmet, instead of a silent grey button.
Feedback
- Every network action has three visible states: in progress (button waiting, label kept), succeeded (snackbar in the past tense), failed (snackbar
errorwith the API message). That is whatrun()inlib/ui.tsdoes. - Snackbar for what is done, dialog for what is about to be done. A confirmation only appears before an irreversible or costly action (delete, restart, stop a container).
- Undo instead of confirm when the action is reversible: snackbar with an "Undo" action or "Detail the snack".
- One snackbar at a time, bottom centre, 4 to 6 seconds, above the navigation bar on mobile.
Empty states, loading, errors
- An empty state says what to do and how long it takes: "Nothing logged yet. The Meal button takes less than 5 seconds."
- Never the text "Loading…" alone: always a loader. Skeleton at final dimensions for content, an 18px indicator inside the working button, an M3E indicator for an isolated wait, a determinate bar with a percentage for a long task. Text may accompany a loader beyond 3 s to say what is happening, never replace it.
- Loading keeps the layout: the previous value stays visible during the update, no full-screen spinner.
- An error stays close to its cause: under the field (
field-help error), in the affected card, or in anerrorsnackbar if it is global.
Forms
- Label always visible above the field, never only as a placeholder.
- Numeric fields with
inputmode="numeric", no arrows, tabular figures. - Validate on blur, not on every keystroke. The submit button stays active and explains what is missing.
- Short choices (symptoms, place, meal type) as filter chips, not a dropdown.
Navigation
- 3 to 5 destinations in the bottom bar or rail. Beyond that, a "More" entry opens a sheet.
- The active item combines three signals:
seccpill, filled icon, weight 600 label. Never colour alone. aria-current="page"on the active destination.Escapecloses sheets and dialogs, focus returns to the trigger.
Accessibility
- 4.5:1 contrast for text, 3:1 for control outlines, meaningful icons and the focus ring, in both themes.
- Status never by colour alone: StatusDot with the word ("Running", "Stopped"), or an icon in the chip.
- A 40px minimum touch target, 48px for primary mobile actions.
- Chart components and gauges with
role="meter"and ARIA values (as MetricCard does). - Reduced motion respected everywhere.
Data and legibility
- Summary before detail: MetricCards at the top of a dashboard, detailed list below.
- Main value large (
metric), context below insmall("3.1 / 8 GB"). - Relative durations and dates when recent ("3 h 05 ago"), absolute beyond a week.
Mobile web and PWA
- No blue tap highlight. On mobile the browser colours the touched element: remove it everywhere with
-webkit-tap-highlight-color: transparent(on*orhtml). Visual feedback comes from the.ststate layer (12% on press) and the ripple, and the keyboard focus stays visible with:focus-visible. - Always a PWA. Every site or web app using Pelote is installable:
manifest.webmanifestwithname,short_name,description,lang,start_url,scope,display: standalone,background_colorandtheme_color(the themesurface), and the AppIcon files:faviconasany192 and 512,maskable192 and 512;<link rel="manifest">,<meta name="theme-color">(one value per theme withmedia),apple-touch-icon180 and the SVG favicon;- a service worker that caches the app shell and shows a useful offline screen (like Stabilité's SOS, which works without a network);
shortcutsto the most used screens when there are any (PoGo Search: 100 IV, Shiny).
Languages and localisation
- French and English at minimum in every app, other languages next. The language follows the device, with a choice in settings; correct
langon the page orLocalein Flutter. - No hard-coded text: everything goes through translation files (ARB in Flutter, message files on the web). No sentence built by concatenation: one complete string with variables, and plurals handled by the language (ICU: "1 day", "3 days").
- Allow +35% length (French and German are longer than English) and test every screen with a lengthened pseudo-translation before shipping.
- Buttons (illustrated in the Localisation spec):
- width follows content, never fixed; never truncated text or a two-line label in a button;
- if two buttons no longer fit side by side, they stack, full width, the primary action on top on mobile;
- if a label is still too long, shorten it in that language (one verb and one object), do not reduce the font size.
- Chips, tabs, navigation: short labels in every language; a chip that is too long wraps in the group, a tab is never truncated (the group scrolls). Navigation labels fit in one word.
- Text: no fixed height on a text block, wrapping allowed everywhere; headings use
text-wrap: balance. - Formats: dates, times, numbers, units and currencies through
Intl(web) orintl(Flutter), never formatted by hand ("3 h 05" in French, "3:05 h" or "3 h 5 min" depending on the language). - Accessibility:
aria-label, tooltips and alternative texts are translated too. Lay out with logical properties (margin-inline,padding-inline-start) to stay ready for right-to-left languages.
Theme personalisation
- Always let people choose, in every app, site or tool:
- the mode: Light, Dark or System (default: System);
- the theme colour: 4 to 6 source colours as swatches, plus a free choice; every role is regenerated from that colour (material-color-utilities,
ColorScheme.fromSeed).
- Android: Material You always available. A "Wallpaper colours" switch (dynamic colour), on by default on Android 12+; when it is on, the colour choice is greyed out with a sentence explaining why.
- The choice applies immediately (no Save button), is remembered, and follows the person across devices when the app has an account.
- The
ok,warn,badstatuses do not change with the theme. - The setting lives in Settings, "Appearance" section, with a live preview; the Plaza and Chrome variants are added there as a "Style" choice.