Uh oh!
There was an error while loading. Please reload this page.
feat: retune the color tokens and add success and warning roles - #30
feat: retune the color tokens and add success and warning roles#30rahulbisht25 wants to merge 6 commits into
Conversation
Light takes the design file's values: ink #111110, an indigo secondary, a hot-pink tertiary, a softer error. Accent containers become washes of their accent (8% accents, 6% statuses) with the accent as the on colour. Dark mirrors the structure with placeholder accents pending the design.
The retuned presets make outline 7% ink and outlineVariant 12%, the reverse of before, so every site swaps token to keep its weight. The menu separator takes outlineVariant instead of its own 10% ink.
# Conflicts: # CHANGELOG.md
Hand-rolled ink alphas become container and outline tokens, disabled content takes onSurfaceDisabled everywhere (a host's colour override no longer shows faded when disabled), and heading, header, title, greeting, bubble and code roles move onto the scale's own cuts.
divyanshub024
left a comment
There was a problem hiding this comment.
Reviewed the full diff at 6204360, plus the enclosing function of every hunk.
Verification
flutter analyzeis clean onlib/and onplayground/. The only two errors repo-wide are pre-existing (example/lib/main.dartwants a generatedenv.g.dart).flutter build web --releasefor the playground builds clean.flow_state_colors.dartis deleted with no dangling references:flowDisabledColoris replaced at every call site, andflowScrimColorwas already dead onmain.- Every
outline/outlineVariantcall site swapped: 15 inlib/, 6 inplayground/. None was missed, and the style-class doc strings that name the default swapped with them. - Nothing in the repo constructs
FlowColors(...), so the eight new required parameters break external hosts only, as the description says.
So nothing here breaks a widget or regresses earlier code at compile time. The findings below are behavioural and contrast regressions, and the worst ones are light-theme only, which is why a screenshot pass in both themes did not surface them.
Contrast under the new light preset
Measured with WCAG 2.1 relative luminance, translucent containers composited over surface#F9F9F7:
| Role | Drawn on | Ratio | AA (4.5:1) |
|---|---|---|---|
tertiary, the markdown link default | surface | 2.55 | fail, was 5.10 |
onWarningContainer | warningContainer | 2.24 | fail |
onTertiaryContainer | tertiaryContainer | 2.37 | fail |
onPrimaryContainer | primaryContainer | 2.60 | fail |
onSuccessContainer | successContainer | 3.34 | fail |
onErrorContainer, the failed user bubble | errorContainer | 4.25 | fail, was 8.20 |
onSecondaryContainer | secondaryContainer | 8.24 | pass |
The same set measures 5.5:1 to 8.8:1 in dark, so the light preset carries all of it. Two of these ship today (the link color and the failed user bubble); the rest land the moment Confirmation and Tool are built on the new roles.
One finding with no diff line to hang on
lib/src/widgets/flow_suggestion.dart:89, the outlined suggestion row is the one ground left hand-rolled.
The CHANGELOG says "Grounds that were hand-rolled ink alphas now take container tokens", and the pass converted the user bubble, the error card, the inline-code chip and both attachment tiles. _outlinedFillOpacity = 0.02, used at line 125, survives, and 0.02 is bit-for-bit surfaceContainerLowest. FlowSuggestionStyle.backgroundColor's doc still reads "Defaults to ink at 2%" while every sibling style doc now names a token. A host retheming through surfaceContainerLowest moves every other faint ground but not this one.
On the description
The Breaking section says the dark outlines land at "10% / 16%". The code ships 0x14 / 0x1F, which is 8% / 12%. The Dart doc, the CHANGELOG and theming.mdx all say 8% / 12%, so the description looks like the stale one.
| tertiary: Color(0xFFA8497B), | ||
| secondaryContainer: Color(0x143730A3), | ||
| onSecondaryContainer: Color(0xFF3730A3), | ||
| tertiary: Color(0xFFFF67B0), |
There was a problem hiding this comment.
The markdown link default drops to 2.55:1 in light, an AA failure on the default rendering path.
FlowMarkdown defaults linkColor to colors.tertiary (lib/src/widgets/flow_markdown.dart:821), and assistant text parts render markdown by default. In light that is now #FF67B0 on surface#F9F9F7, which measures 2.55:1. The old #A8497B measured 5.10:1. Link text draws at bodyLarge 16px, so AA wants 4.5:1. On tertiaryContainer it is 2.37:1.
Dark is fine at 6.68:1, which is why the screenshot pass read it as legible on both grounds.
If the hot pink has to stay as the accent, the link could take a darkened cut of it in light while tertiary itself stays the design's value.
| errorContainer: Color(0xFFFEE2E2), | ||
| onErrorContainer: Color(0xFF7F1D1D), | ||
| errorContainer: Color(0x0FC14A4A), | ||
| onErrorContainer: Color(0xFFC14A4A), |
There was a problem hiding this comment.
The failed user bubble's text falls from 8.2:1 to 4.25:1 in light.
FlowMessage._buildUser paints a failed user turn with errorContainer as fill and onErrorContainer as ink (lib/src/widgets/flow_message.dart:191-196), and both bypass every style override by design. With the container now a 6% wash and the on color the error red itself, light composites to #C14A4A on #F6EFED, which is 4.25:1, just under the 4.5:1 floor for 16px prose. Before this PR it was #7F1D1D on the opaque #FEE2E2 at 8.2:1.
A user whose message failed to send now reads the least legible text in the thread. Dark is fine at 5.79:1.
| onSuccessContainer: Color(0xFF249655), | ||
| warning: Color(0xFFEE8D34), | ||
| onWarning: Color(0xFFFFFFFF), | ||
| warningContainer: Color(0x0FEE8D34), |
There was a problem hiding this comment.
The "onContainer is the accent itself" rule yields 2.2:1 to 3.3:1 text in light.
Over a 6% to 8% wash of the same accent, the light preset measures:
onWarningContainer#EE8D34on#F8F3EC: 2.24:1onTertiaryContainer: 2.37:1onPrimaryContainer: 2.60:1onSuccessContainer: 3.34:1
Only onSecondaryContainer clears AA comfortably, at 8.24:1. These eight roles exist "for the upcoming Confirmation and Tool states and for hosts", and theming.mdx documents the accent as the on-color to use, so the first component built on them ships text that cannot pass an audit. Dark measures 5.5:1 to 8.8:1, so this is a light-preset problem only.
A darker on cut per accent in light, keeping the wash as the container, would preserve the rule's look and clear AA.
| onInverseSurface: Color(0xFF1A1A19), | ||
| inversePrimary: Color(0xFF8C3A67), | ||
| outline: Color(0x14FFFFFF), | ||
| outlineVariant: Color(0x1FFFFFFF), |
There was a problem hiding this comment.
The dark hairline drops from 20% to 12%, and the reason for the extra step went with the old doc.
The comment this replaces read: "Hairline around a raised card, 14% ink in light, 20% in dark; dark hairlines need the extra step to hold the edge." outlineVariant is now 12% in both themes, so nothing re-establishes that step. Against #171717 the hairline falls from 1.87:1 to 1.42:1.
Every dark site that swapped outline to outlineVariant loses 40% of its edge: the attachment tile (flow_attachment_group.dart:330), the retry pill (flow_error_state.dart:227), the menu sheet, and the playground's variant pills. The one I would look at first is the composer's inactive send ring (flow_composer.dart:268): that ring is the disc's only boundary when there is nothing to send, and at 12% white on #1E1E1E it measures 1.44:1, well under the 3:1 WCAG minimum for a UI component boundary.
Light is a much smaller move, 14% to 12%, so the swap really does hold its weight there.
| static const double _jumpShadowOpacity = 0.08; | ||
| /// The jump button's lift: the composer's shadow, so the disc and the | ||
| /// card it floats above share one. | ||
| static const double _jumpShadowOpacity = 0.02; |
There was a problem hiding this comment.
The jump-to-latest disc loses 4x its lift, on the one widget the PR says was never exercised.
The disc's ground is colors.surface (line 381), the same color as the page it floats over, so its only separation from messages sliding underneath is the hairline plus this shadow. The comment this replaces gave the reason for the 0.08: "stronger on the small floating disc so it separates from the content scrolling beneath it."
Matching the composer's 0.02 is not quite like for like: the composer sits on surfaceBright, a different ground from the page, and nothing scrolls under it. In dark it is worse still, because colors.onSurface is #FFFFFF, so this paints a 2% white glow rather than a shadow.
The Testing section notes jump-to-latest was not exercised because the scripted reply is too short to scroll, so nothing has checked that the disc still reads over a moving thread. Worth a look with a long thread in both themes before merge.
| barrierColor: style?.barrierColor, | ||
| // The sheet is the raised card again: top corners at the card radius, | ||
| // the same firm hairline the anchored menu carries. | ||
| // edged in the palette's firm hairline. |
There was a problem hiding this comment.
The sheet's edge and the anchored card's edge have diverged.
flowMenuBorderColor, used on line 67 below, is read only here; the anchored card paints _borderOpacity 0.2 fading to _borderFadeOpacity 0.12 directly (flow_menu_core.dart:140-143). Both were 20% ink before this PR, which is what the old comment meant by "the same firm hairline the anchored menu carries".
Under FlowMenuPresentation.auto one FlowMenu now draws a 20% edged card on desktop and a 12% edged sheet on phone. The sheet's outer edge is also the exact same weight as its own FlowMenuRule separators now, since both resolve to outlineVariant, so the card boundary flattens against its internal section rules.
Moving the card's gradient onto the tokens too would settle both.
| @@ -17,7 +16,6 @@ import 'flow_state_colors.dart'; | |||
| /// triggers use while open. | |||
| const double _borderOpacity = 0.2; | |||
| const double _borderFadeOpacity = 0.12; | |||
There was a problem hiding this comment.
_borderFadeOpacity is 0.12, which is bit-for-bit the new outlineVariant (0x1F111110 / 0x1FFFFFFF, 12.2% ink in both themes). Since this pass moved the sheet's border and the separator onto the token, the card's gradient end at line 143 could take it too.
As it stands, a host that overrides outlineVariant to firm up hairlines moves the sheet's edge and every other firm hairline but leaves the anchored card's gradient at the preset value, so the two drift. Using the token here would also make the 0.2 start read as the one deliberate exception rather than one of a pair.
| chipFill: | ||
| _mdStyle?.codeChipColor ?? | ||
| context.flowColors.onSurface.withValues(alpha: _inlineCodeWash), | ||
| _mdStyle?.codeChipColor ?? context.flowColors.surfaceContainer, |
There was a problem hiding this comment.
The inline-code chip lands at 6%, matching neither of the two things it was defined against.
The constant this replaces was documented as "the code block's and user bubble's 4% ink". After this PR the code block's ground is surfaceContainerLowest at 2% (flow_code_block.dart:237) and the user bubble is surfaceContainerLow at 4% (flow_message.dart:192), while the chip is surfaceContainer at 6%, here and at line 838.
That is a 50% darkening plus the loss of the relationship the constant existed to express. surfaceContainerLow is the exact token the old 0.04 mapped to. If the darkening is deliberate, the CHANGELOG line reads like a like-for-like token swap beside the error card and could say the chip deepened.
| Color role names follow Material 3's `ColorScheme`, so an existing M3 scheme | ||
| maps straight across — with one addition. The Flow design draws content at | ||
| maps straight across — with two additions. The Flow design draws content at |
There was a problem hiding this comment.
The docs still promise a straight M3 map, which is no longer true for the outlines.
FlowColors' own doc calls this out: "with one inversion: here outline is the faint hairline and outlineVariant the firm one, the reverse of M3's weights", and the PR lists it as breaking. This page, which is what a host reads before theming, says only "maps straight across, with two additions", and names those two additions as the third ink level and the status groups.
A host that copies its M3 outline and outlineVariant across gets every hairline in the library at the wrong weight with nothing in the docs to explain it. Worth a third bullet here, or a callout, since this is the one role pair that does not map.
| /* Light: FlowColors.light — #F9F9F7 warm paper, #1A1A19 ink. */ | ||
| /* Light: FlowColors.light — #F9F9F7 warm paper, #111110 ink. */ | ||
| :root[data-theme='light'] { | ||
| --sl-color-accent-low: #f6e9ed; |
There was a problem hiding this comment.
The Starlight accent ramp still mirrors the container colors this PR deleted.
--sl-color-accent-low: #f6e9ed and --sl-color-accent-high: #8c3a67 are the old light primaryContainer and onPrimaryContainer; lines 29 and 31 carry the old dark pair, #432b37 and #f5cfe1. Those values no longer exist anywhere in the palette: primaryContainer is now rgba(224, 113, 167, 0.08) and onPrimaryContainer is #E071A7.
--sl-color-accent-high drives link and aside text across the docs site (lines 463, 510, 1116), so the docs render brand colors the library no longer ships. The ink and outline mirrors right below were updated, so this looks like an oversight rather than a decision.
--flow-outline-hi: 0.18 in both blocks is orphaned the same way, since no token is 18% any more.
Summary
The color presets are retuned to the design file and two roles are added.
#111110, an indigosecondary, a hot-pinktertiary(markdown links follow), a softererror.Containeris its accent at an alpha — 8% for primary, secondary and tertiary, 6% for error, success and warning — and eachonContaineris the accent itself, replacing the opaque pastels.successandwarning, each withon,ContainerandonContainer, for the upcoming Confirmation and Tool states and for hosts.outline/outlineVariantswap meaning.outlineis now the faint hairline (7% ink) andoutlineVariantthe firm one (12%), the reverse of before; every site swaps token to keep its weight, and the menu separator takesoutlineVariantin place of its own 10% ink.#171717, outlines a step firmer (10% / 16%),primaryandtertiaryshared with light. The other dark accents are placeholders pending the design file.Docs that quote numbers follow: the
FlowColorsclass and field docs,theming.mdx, the docs site'stheme.cssmirror, CLAUDE.md, and a Colors entry in CHANGELOG 0.3.0.Breaking
outlineandoutlineVariantchange meaning as above.FlowColorsconstructor parameters.copyWithon a preset — the documented path — is unaffected; a host constructingFlowColors(...)from scratch must supply them.Testing
flutter analyzeclean at the root, inplayground/and inexample/;dart formatclean.Note
Medium Risk
Breaking theme-token semantics and required
FlowColorsfields affect any host that constructs palettes manually or assumed M3-aligned outline weights; visual regressions are library-wide but widget APIs stay the same.Overview
Retunes FlowColors to the design file and threads those tokens through widgets, docs, and the docs-site CSS mirror. Ink moves to
#111110, accents get indigo secondary, hot-pink tertiary (links), softer error, and newsuccess/warninggroups with accent-wash containers (8% accents, 6% statuses). Breaking:outlineis now the faint hairline (7%/8%) andoutlineVariantthe firm one (12%)—call sites swap which token they use so visual weight stays—andFlowColors(...)requires eight new roles (presets andcopyWithunchanged).Components stop using hand-rolled ink alphas for fills and borders: user bubbles, error cards, inline code, attachment tiles, markdown chrome, menus/sheets, pills, suggestions, code blocks, composer send ring, and jump-to-latest shadow/hairline all map to
surfaceContainer*and the corrected outline roles. Disabled UI consistently usesonSurfaceDisabledinstead of fading host overrides viaflowDisabledColor(flow_state_colors.dartremoved ).Typography defaults shift without API changes: wide greeting uses
headlineMedium, markdown#–###use emphasised title cuts, code block headerslabelMedium, error titlesbodyMediumEmphasised, bubble prose on body 1.5 (composer field stays 1.3), monocodeat 14, message actions 15px icons on 20px frames 4 apart withsurfaceContainerhover wash.Reviewed by Cursor Bugbot for commit 6204360. Bugbot is set up for automated code reviews on this repo. Configure here.