Skip to content

design(soliplex_design): back the surface tonal family with real tokens (surfaceContainer / surfaceDim / surfaceBright) #425

Description

@91jaeminjo

Summary

Three Material surface roles are not backed by real surface tokens — they borrow
unrelated tokens whose values are wrong for the role:

ColorScheme role Currently sourced from Should be
surfaceContainer colors.inputBackground (a text-field fill) a real mid container rung
surfaceDim colors.accent (a misnamed neutral) the dimmest surface
surfaceBright colors.background (the base surface) the brightest surface

This is the same defect class as #418: a role painted from a token that does not
carry the right value for that role. It leaves the surface tonal ladder
non-monotonic and inverts surfaceDim / surfaceBright in dark mode.

Surfaced while fixing #418 (PR #422). This is the surfaces-are-mis-valued half
of the problem; #421 is the surfaces-aren't-settable half — both fell out of
#418.

Evidence

packages/soliplex_design/lib/src/theme/theme.dart:72,75,76

surfaceContainer: colors.inputBackground,
surfaceDim:       colors.accent,
surfaceBright:    colors.background,

Sorting the dark-mode greys by lightness (M3 dark: higher elevation = lighter;
surfaceDim should be darkest, surfaceBright brightest):

Role Dark Correct?
surfaceContainerLowest 0x0E
surfaceBright (←background) 0x11 ✗ should be the brightest (~0x36)
surface 0x11
surfaceContainerLow 0x1A
surfaceContainerHigh 0x2A
surfaceDim (←accent) 0x2A ✗ should be the dimmest (~0x0C)
surfaceContainer (←inputBackground) 0x33 ✗ should sit ~0x22, between Low and High
surfaceContainerHighest 0x33
  • surfaceDim is the worst: in dark it is brighter than surface, so the
    "dimmest" surface is one of the brightest — fully inverted. accent is itself
    a misnamed neutral (dark 0x2A2A2A, light 0xE9EBEF), and its only
    production consumer is this line — accent / onAccent are otherwise dead.
  • surfaceBright is correct in light (white) but in dark collapses onto
    surface (0x11), so there is effectively no bright surface in dark mode.
  • surfaceContainer is the actively-used one (card, chip, selected list
    tile, collapsed expansion tile, popup menu). Mislayered in both palettes.
  • Not in scope: inverseSurface / onInverseSurfacefix(soliplex_design): paint component surfaces with surface roles, not onPrimary #422 already
    repointed these correctly (foreground / background, a guaranteed contrast
    pair), so they're excluded here. This follow-up is only the three roles above.

Impact

Proposed fix (complete)

  1. Add three real named tokens to SoliplexColors (light + dark), completing the
    M3 surface set (background + surfaceContainerLowest/Low/High/Highest
    already exist): surfaceContainer, surfaceDim, surfaceBright. Values
    chosen for correct M3 ordering. This is an intentional visual change and a
    design call — needs sign-off on the exact values.
    Rough targets:
    • surfaceContainer: ~0x22 dark / ~0xEE light (between Low and High)
    • surfaceDim: darkest surface in dark (~0x0C) / dimmest in light (below Highest)
    • surfaceBright: brightest in dark (~0x36) / white in light
  2. Map them straight through in buildSoliplexThemeData
    (surfaceContainer/Dim/Bright: colors.<same>).
  3. Repoint the structural fills currently reading colors.inputBackground at
    colors.surfaceContainer: card, chip, listTile.selectedTileColor,
    expansionTile.collapsedBackgroundColor, and the popup menu (it merged on
    colors.inputBackground).
  4. Leave inputDecoration.fillColor on colors.inputBackground — a genuine
    input token, correctly named for its sole remaining use.
  5. Retire accent / onAccent (dead once surfaceDim has a real token).
    Removing a public token is a breaking change to the SoliplexColors
    contract + mirror — decide whether to remove or leave unused.

This backs the whole surface family with correctly-valued, correctly-named
tokens. It is the complete fix.

Lighter interim alternative

Add only surfaceContainer, set to today's inputBackground values; leave
surfaceDim / surfaceBright as-is. Decouples the input fill with no golden
churn
, but leaves the ladder non-monotonic and surfaceDim / surfaceBright
inverted in dark. Only worth it to defer the golden/design loop; it does not fix
the root defect.

Required by project rules

  • Adding tokens needs explicit approval before code is written.
  • Tokens must be mirrored into design_system/tokens.{dart,css,jsx} in the same
    change, and design_system/README.md updated. design_system/CLAUDE.md and
    scripts/content.js also describe the current double-use of inputBackground
    and the accent token, so those notes need correcting.
  • Component goldens are a Linux baseline — regenerate on CI, not locally (the
    complete fix shifts card/chip/tile fills).

Test impact

  • Full SoliplexColors(...) constructions gain the new fields (2 palettes, 2
    mirror copies, theme_test.dart, colors_test.dart).
  • Assertion updates: theme_test.dart:145 (cs.surfaceContainer), :151
    (surfaceDim), :152 (surfaceBright), :204 (popup); brand_lowering_test
    gains/loses the accent lines depending on the retire decision.

Not required

  • Renaming inputBackground. After this fix it is used only for the text-input
    fill, so its name matches its role. Renaming it would be a cosmetic,
    contract-breaking change to do deliberately or not at all.

Refs #418, #421, #422.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions