← All articles
8 min readColor,Design Systems,Visual Comfort,Accessibility

The Flowo Color System: Tokens, Dark Mode and Measured Contrast

By Maksym Bardakh Ivko · Chief Executive Officer

In short

Flowo's color system has four brand anchors (White #F9F9F9, Blue #004E72, Orange #FF6E42, Navy #092634), and screens reach them only through semantic roles that carry separate light and dark values. Primary text measures 14.06:1 on the light page and 14.90:1 on the dark page. BBMM Technologies gives every status color a second value for text, because the system fill colors measure between 1.97:1 and 3.18:1 on our light page.

Four anchors and one layer of roles

Flowo's palette starts from four brand colors: White #F9F9F9, Blue #004E72, Orange #FF6E42 and Navy #092634. They are constants in a class named AppleHigPalette, next to the surface values we derived from them.

Screens read a second class, AppColors, which defines roles such as textPrimary and actionBrand. Each role is a Flutter CupertinoDynamicColor with eight slots: light and dark, each in normal and high contrast, each again for raised surfaces such as sheets. Flutter picks the slot from the widget's context, so one role name returns the correct value in every appearance.

A role that is painted without being resolved shows its light value. Brand Blue on the navy page measures 1.74:1, so this mistake is visible the first time someone opens the screen in dark mode.

Surface values in light and dark

Neither appearance in Flowo uses #000000 or #FFFFFF. The surfaces are these:

  • Light: page #F2F2F7, card #F9F9F9, sheet #FCFCFE, field fill #E9E9EE.
  • Dark: page #092634, card #12334A, sheet #173D57, field fill #1D4863.
  • Dark only: a sunken step, #061A24, for header bands that must read as recessed.

Cards are lighter than the page in both appearances. The steps are small on purpose. A card against the page measures 1.06:1 in light and 1.19:1 in dark, so hierarchy comes from the tonal step plus a hairline. Shadows are drawn in zinc greys at low opacity.

We keep away from the two extremes because a planner gets opened many times a day. White on black is 21:1, the largest luminance jump a screen can show. Our strongest pair is 14.90:1.

Measured text contrast

Every contrast ratio we publish for Flowo comes from the WCAG formula. Relative luminance is L = 0.2126 R + 0.7152 G + 0.0722 B on linearized sRGB channels, and the ratio is (L1 + 0.05) / (L2 + 0.05). Navy has L = 0.0169 and the light page has L = 0.8910, which gives 0.9410 / 0.0669 = 14.06.

  • Navy text on the light page: 14.06:1. On a light card: 14.90:1.
  • White #F9F9F9 text on the dark page: 14.90:1. On a dark card: 12.48:1. On a dark sheet: 10.83:1.
  • Secondary text in light (#3C3C43 at 75% opacity): 5.03:1 on a card, 4.82:1 on the page, 4.64:1 on a field fill.
  • Secondary text in dark (#EBEBF5 at 60% opacity): 5.62:1 on the page, 5.00:1 on a card, 4.54:1 on a sheet.

The light secondary label is where we left the iOS default. iOS draws secondary labels at 60% opacity. On our card that gives 3.35:1, under the 4.5:1 that WCAG 1.4.3 asks of body text. At 75% the same grey clears 4.5:1 on all three light surfaces.

Translucent colors are composited onto the surface they sit on before we measure them. A ratio taken from the raw translucent value describes a color that never reaches the screen.

One tint for controls and a second value for links

Brand Blue is the only color in Flowo that means a control can be tapped. In light it is #004E72, and a white label on it measures 8.55:1. In dark the fill is #4AA6D2 with a navy label at 5.74:1.

Link text in dark mode uses a different blue. The fill blue measures 4.17:1 on a dark sheet, which fails 4.5:1. Links use #5AB0D8 instead: 6.45:1 on the page and 4.69:1 on a sheet.

Orange is a fill and never text. As text on the light page it measures 2.49:1, and a white label on orange measures 2.64:1. A navy label on orange measures 5.65:1, so that is the pair we ship.

The label color on any fill is computed. A function named textOn measures the fill against navy and against off-white and takes the higher result. If that result is under 4.5:1, it switches to a high-contrast anchor on the same side, #09090B or #FAFAFC.

How color carries state

Status colors in Flowo come in pairs: one value to fill and one value to read. The system green, orange and red are tuned as fills, and as text on our light page they fall short.

  • Success: system green #34C759 measures 1.99:1. Text uses #1E7B3C (4.76:1) in light and #5FD17F (8.15:1) in dark.
  • Warning: system orange #FF9500 measures 1.97:1. Text uses #9A5A00 (4.91:1) in light and #FFB340 (8.80:1) in dark.
  • Destructive: system red #FF3B30 measures 3.18:1. Text uses #B3261E (5.86:1) in light and #FF8A80 (6.87:1) in dark.

A label such as "Delete category" is small type whose meaning has to be certain, so it takes the text value. A filled destructive button keeps system red, because a fill is judged against its own label.

The focused field has a border role of its own. Decorative borders measure 1.64:1 on a dark card, which is acceptable for a rule that nobody has to find. The focus edge is #7C7C81 in light (3.72:1 on the page) and #6A97B8 in dark (4.21:1 on a card, 3.66:1 on a sheet). WCAG 1.4.11 asks for 3:1.

Tests that hold the numbers

Flowo's contrast ratios are asserted in unit tests, and flutter test is one of the gates our CI runs.

  • A foundation test checks every accent's text value at 4.5:1 on three light surfaces and four dark surfaces, and the focus edge at 3:1.
  • A form test measures ten ink and surface pairs of the form system in both appearances and prints each ratio to the log.
  • The onboarding contrast test writes out its own luminance function, so it cannot agree with the app by sharing the app's arithmetic.
  • The focus timer draws text over a gradient. Its test measures the ink against every gradient stop and against break blocks drawn at 30%, 50% and 100% opacity.
  • A guard test scans screen and widget files for iOS system blue, so the earlier tint cannot return.

We measure against the hardest surface of each appearance. A dark sheet is the lightest dark surface, and a color checked only against the page can pass and then fail inside a modal.

Known limits

Flowo's seven event swatches are the part of the palette we have measured and not yet corrected. Against a light card they range from 3.38:1 to 5.08:1. Against a dark card they range from 2.46:1 to 3.70:1, and four of the seven are under 3:1. A comment in the source describes them as AA-safe, and our computation does not support that for dark mode.

flowoapp.com uses the same surface values and is light only. Its stylesheet holds the dark tokens in step with the app, and the site's code removes the dark class, so those tokens do not render today.

Key takeaways

  • Flowo's four brand anchors are White #F9F9F9, Blue #004E72, Orange #FF6E42 and Navy #092634, and screens read them through semantic roles with eight variants each.
  • Primary text measures 14.06:1 on the light page (#092634 on #F2F2F7) and 14.90:1 on the dark page (#F9F9F9 on #092634).
  • Secondary text in light mode is drawn at 75% opacity because the iOS default of 60% gives 3.35:1 on our card.
  • Success, warning and destructive each have a separate text color, since system green, orange and red measure 1.99:1, 1.97:1 and 3.18:1 on the light page.
  • Four of the seven event swatches measure under 3:1 against a dark card, and we list that as an open issue.

Frequently asked questions

What colors does the Flowo app use?
Flowo uses four brand colors: White #F9F9F9, Blue #004E72, Orange #FF6E42 and Navy #092634. The light page is #F2F2F7 with #F9F9F9 cards, and the dark page is navy #092634 with #12334A cards. Blue marks controls and orange is a highlight fill.
Why does Flowo avoid pure black and pure white?
White on black is 21:1, the largest luminance jump a screen can show, and a planner gets opened many times a day. Our strongest text pair is 14.90:1, which is more than twice the 7:1 that WCAG sets for enhanced contrast. A unit test asserts that no surface or label anchor is #000000 or #FFFFFF.
How do you calculate a contrast ratio from two hex colors?
Convert each sRGB channel to linear light, compute relative luminance as 0.2126 R + 0.7152 G + 0.0722 B, then divide the lighter luminance plus 0.05 by the darker luminance plus 0.05. For navy #092634 on #F2F2F7 the luminances are 0.0169 and 0.8910, and the ratio is 14.06:1. A translucent color has to be blended onto its background first.
Why does dark mode use a different blue for links than for buttons?
The dark button fill, #4AA6D2, measures 4.17:1 on a dark sheet, which is under the 4.5:1 required for text. Flowo uses #5AB0D8 for link text in dark mode, which measures 6.45:1 on the page and 4.69:1 on a sheet. The fill blue stays on buttons, where a navy label on it measures 5.74:1.
Can system green, orange or red be used as text on a light background?
On Flowo's light page they measure 1.99:1, 1.97:1 and 3.18:1, so none of them reaches 4.5:1. We keep them as fills and use darker partners for text: #1E7B3C, #9A5A00 and #B3261E. Those measure 4.76:1, 4.91:1 and 5.86:1 on the same page.

References

About the author

Maksym Bardakh Ivko

Chief Executive Officer

Maksym leads product at BBMM Technologies. He designs Flowo’s scheduler and writes most of what ships in it.