Skip to content
sysout.dev

In My Head — Visual Design and Theme Architecture

Complete Material 3 light and dark colour schemes derived from the brand source palette for In My Head, with semantic role mapping and accessibility validation.

This document defines how In My Head’s brand source palette becomes a complete, accessible Material 3 colour system.

It complements the finalized brand guidelines doc (name, logo, wordmark, and source palette). The brand guidelines define the literal identity assets and source palette; this document defines how that palette becomes an in-app colour system.

Status

Implemented in the Android MVP; continuing visual verification on device

Last reviewed

August 2026

Role mapping and delivery boundaries in this document are settled decisions. Exact tonal values are computed and contrast-verified below, but remain provisional until viewed on production screens and physical devices.

Purpose and scope

The objective is to establish a deliberate, accessible, complete Material 3 colour language for In My Head from the four brand seed colours, before any screens are built.

The review covers:

  • Complete Material 3 light and dark colour schemes, computed from the brand seeds using the actual HCT tonal-palette algorithm (not hand-picked approximations).

  • Semantic mapping of each role to what it means in this app specifically.

  • Contrast validation for every meaningful pair.

  • One explicit decision about a brand colour that stays outside the core role system.

The review does not cover:

  • Logo, wordmark, or name lockup construction — that belongs in the brand guidelines doc.

  • Font-family selection and component layout beyond the role mapping. The finalized Comfortaa wordmark treatment and Baloo 2 Material text family are defined in the brand guidelines and implemented in the app theme.

  • KMP-specific theming plumbing (Android ColorScheme vs. future iOS/Desktop equivalents).

  • Using tertiary or other roles merely to create variety.

Starting point

Brand source palette

Source role Value Chosen purpose

Primary seed

#FF0052

Pink-red. Reserved for the principal action — the record control — matching the near-universal "red = recording" convention used by cameras, voice recorders, and call apps.

Secondary seed

#0055DA

Blue. Supporting actions and controls that shouldn’t compete with the record affordance.

Tertiary seed

#00C68D

Teal-green. Accent and "processed / complete" states — a transcript that’s been cleaned up.

Reserved accent

#FFD400

Yellow. Kept outside the core role system rather than forced into a fourth role — see Reserved accent: yellow.

These colours are identity seeds, not four literal Material role values. The roles below are computed tonal derivatives of these seeds, chosen for accessibility — not the raw brand hex applied directly.

Identity families

  • Pink-red is the primary identity seed — reserved for the principal action and limited brand emphasis, not spread across ordinary UI.

  • Blue supplies the secondary family — calmer supporting actions and controls.

  • Teal supplies the tertiary family — accent and completion states.

  • Yellow remains a reserved accent, not a core role.

  • Error remains a separate, standard red family and is deliberately not derived from the brand pink-red. Primary is already a saturated red-adjacent hue here; deriving error from the same family would blur "this is the main action" and "something’s wrong" — the one thing a red error state most needs to avoid.

Design principles

Roles are semantic

Colour is assigned according to what a component communicates. Two components may use different colours when they represent different states or categories, but not merely to add variety.

Primary is selective

Primary is reserved for the record control, strong actions, and limited brand emphasis. Ordinary headings, transcript text, timestamps, and passive icons use surface foreground roles, not primary.

Selection uses a container

Selected list items and selected navigation destinations use primaryContainer with onPrimaryContainer. Selection also carries a non-colour cue (shape, position, or typography) — never colour alone.

Surfaces express hierarchy

Surface and surface-container roles distinguish the screen, cards, and grouped regions, with deliberately restrained steps between them. A surface-container level looks similar to base surface because it expresses hierarchy, not a semantic category — secondaryContainer and tertiaryContainer remain distinguishable enough to communicate their separate purpose.

Boundaries use outline roles

outline is used when a component boundary must stay clearly identifiable. outlineVariant is for quiet dividers. Primary is not the default border or divider colour.

Content dominates its container

Transcript text and labels must remain more prominent than their surrounding surfaces. Large saturated containers are avoided around long-form transcript content.

Complete Material 3 role contract

Every role in the stable Material 3 ColorScheme is explicitly defined below for both light and dark schemes — nothing is left to library defaults.

Computed using Google’s HCT tonal-palette algorithm (materialyoucolor, the same math behind Android’s own dynamic colour system), spec version 2021, TonalSpot tone mapping, against the four brand seeds plus a standard (non-brand-derived) error seed.

Light scheme

Role Hex Tone

primary

#BE003B

40

onPrimary

#FFFFFF

100

primaryContainer

#FFDADB

90

onPrimaryContainer

#91002B

30

inversePrimary

#FFB2B7

80

secondary

#0054D7

40

onSecondary

#FFFFFF

100

secondaryContainer

#DBE1FF

90

onSecondaryContainer

#003FA5

30

tertiary

#006C4B

40

onTertiary

#FFFFFF

100

tertiaryContainer

#62FDBF

90

onTertiaryContainer

#005138

30

background

#FFF8F7

98

onBackground

#22191A

10

surface

#FFF8F7

98

onSurface

#22191A

10

surfaceVariant

#F8DCDD

90

onSurfaceVariant

#554243

30

surfaceTint

#BE003B

40

surfaceBright

#FFF8F7

98

surfaceDim

#E7D6D6

87

surfaceContainerLowest

#FFFFFF

100

surfaceContainerLow

#FFF0F0

96

surfaceContainer

#FCEAEA

94

surfaceContainerHigh

#F6E4E4

92

surfaceContainerHighest

#F0DEDE

90

inverseSurface

#382E2E

20

inverseOnSurface

#FFEDED

95

error

#BA1A1A

40

onError

#FFFFFF

100

errorContainer

#FFDAD6

90

onErrorContainer

#93000A

30

outline

#887273

50

outlineVariant

#DBC0C1

80

scrim

#000000

0

primaryFixed

#FFDADB

90

primaryFixedDim

#FFB2B7

80

onPrimaryFixed

#40000E

10

onPrimaryFixedVariant

#91002B

30

secondaryFixed

#DBE1FF

90

secondaryFixedDim

#B3C5FF

80

onSecondaryFixed

#00184A

10

onSecondaryFixedVariant

#003FA5

30

tertiaryFixed

#62FDBF

90

tertiaryFixedDim

#3DDFA4

80

onTertiaryFixed

#002114

10

onTertiaryFixedVariant

#005138

30

Dark scheme

Role Hex Tone

primary

#FFB2B7

80

onPrimary

#67001C

20

primaryContainer

#91002B

30

onPrimaryContainer

#FFDADB

90

inversePrimary

#BE003B

40

secondary

#B3C5FF

80

onSecondary

#002A76

20

secondaryContainer

#003FA5

30

onSecondaryContainer

#DBE1FF

90

tertiary

#3DDFA4

80

onTertiary

#003825

20

tertiaryContainer

#005138

30

onTertiaryContainer

#62FDBF

90

background

#1A1112

6

onBackground

#F0DEDE

90

surface

#1A1112

6

onSurface

#F0DEDE

90

surfaceVariant

#554243

30

onSurfaceVariant

#DBC0C1

80

surfaceTint

#FFB2B7

80

surfaceBright

#413737

24

surfaceDim

#1A1112

6

surfaceContainerLowest

#140C0D

4

surfaceContainerLow

#22191A

10

surfaceContainer

#271D1E

12

surfaceContainerHigh

#312828

17

surfaceContainerHighest

#3D3233

22

inverseSurface

#F0DEDE

90

inverseOnSurface

#382E2E

20

error

#FFB4AB

80

onError

#690005

20

errorContainer

#93000A

30

onErrorContainer

#FFDAD6

90

outline

#A38B8C

60

outlineVariant

#554243

30

scrim

#000000

0

primaryFixed

#FFDADB

90

primaryFixedDim

#FFB2B7

80

onPrimaryFixed

#40000E

10

onPrimaryFixedVariant

#91002B

30

secondaryFixed

#DBE1FF

90

secondaryFixedDim

#B3C5FF

80

onSecondaryFixed

#00184A

10

onSecondaryFixedVariant

#003FA5

30

tertiaryFixed

#62FDBF

90

tertiaryFixedDim

#3DDFA4

80

onTertiaryFixed

#002114

10

onTertiaryFixedVariant

#005138

30

Fixed roles are identical across both schemes by design — they’re for content that must stay visually stable regardless of light/dark mode (e.g. a persistent brand chip).

Literal brand hex vs. in-app tonal derivative

Worth calling out explicitly, since two of the three seeds shift a fair amount from the raw brand swatch once run through the accessibility-safe tone mapping:

  • Primary — raw #FF0052 → in-app #BE003B (light) / #FFB2B7 (dark). The raw swatch is too light/vibrant to safely carry white text at body-text sizes, so the role uses a slightly deeper, equally-recognisable red. The literal #FF0052 stays a valid brand constant for the icon, splash, and marketing use — just not for the primary role’s UI surfaces.

  • Secondary — raw #0055DA → in-app #0054D7. Near-identical; blue happened to already sit close to the accessible tone.

  • Tertiary — raw #00C68D → in-app #006C4B (light) / #3DDFA4 (dark). The raw teal is quite light already, so its accessible "base" role tone darkens noticeably. If you want the original bright teal to actually show up recognisably in the UI, that’s tertiaryContainer (#62FDBF) or tertiaryFixedDim, not the base tertiary role.

Reserved accent: yellow

#FFD400 is not mapped into the core primary/secondary/tertiary/error system. Yellow has an unusually narrow accessible range — there’s very little room where a yellow fill and readable text on top of it both work at once, so forcing it into a full role (with an onYellow foreground) tends to fight the "clean look" goal rather than serve it.

Instead, treat it as a single reserved, non-text accent for one deliberate moment — e.g. a subtle "processing" pulse while the local LLM cleanup step runs — used sparingly and never as a text-bearing surface.

Tone Hex

T30 (darkest, usable as text on light surfaces)

#554500

T80 (usable as a light fill)

#EBC300

T90 (very light, container-like)

#FFE177

This is genuinely optional for MVP — it can be dropped entirely without losing anything structural.

App colour direction

Production mappings

  • Record control (principal action): primary with onPrimary.

  • Recording-active indicator (pulse/dot while live): primary — reinforces the record convention rather than introducing a second red.

  • Secondary controls (pause, cancel, settings entry): secondary/onSecondary for emphasis, onSurfaceVariant for low-emphasis icons.

  • Transcript-ready / cleanup-complete confirmation: tertiaryContainer with onTertiaryContainer.

  • Selected list item or nav destination: primaryContainer with onPrimaryContainer.

  • Ordinary headings, transcript text, filenames: onSurface.

  • Supporting text (timestamps, metadata, durations): onSurfaceVariant.

  • Strong control borders: outline.

  • Quiet dividers: outlineVariant.

  • Destructive action (delete a recording or transcript): error family.

  • Processing/in-progress bespoke highlight: the reserved yellow accent, non-text only, used sparingly.

Accessibility and validation

Every pair below is computed from actual tone values via the same contrast math Material uses internally to guarantee its role-pair targets — not eyeballed.

Pair Light Dark

primary / onPrimary

6.46:1

7.72:1

primaryContainer / onPrimaryContainer

7.24:1

7.24:1

secondary / onSecondary

6.46:1

7.72:1

secondaryContainer / onSecondaryContainer

7.24:1

7.24:1

tertiary / onTertiary

6.46:1

7.72:1

tertiaryContainer / onTertiaryContainer

7.24:1

7.24:1

error / onError

6.46:1

7.72:1

errorContainer / onErrorContainer

7.24:1

7.24:1

background / onBackground

16.31:1

14.35:1

surface / onSurface

16.31:1

14.35:1

surfaceVariant / onSurfaceVariant

7.24:1

5.49:1

inverseSurface / inverseOnSurface

11.59:1

10.18:1

surface / outline (boundary, non-text)

4.27:1

5.85:1

WCAG AA requires 4.5:1 for normal text and 3:1 for UI-component boundaries/non-text. Every text-bearing pair here clears AA, and most clear AAA (7:1). outline is a boundary role, not text, so it’s validated against the 3:1 non-text minimum — both schemes clear it comfortably.

Non-colour cues still apply on top of this: selected states, errors, and links must not rely on colour alone (shape, icon, underline, or position as a second signal).

Open decisions

  • Typography scale (display/headline/title/body/label sizes) — separate pass. Typeface itself is chosen (Baloo 2 for UI text, see brand-guidelines.adoc), but mapping it onto Material 3’s type roles isn’t done yet.

  • Whether the dark-theme primary red reads correctly on real hardware (especially AMOLED) — needs physical-device verification, not just simulator review.

  • Whether the yellow accent ships at all in MVP, or gets deferred entirely.

  • Logo, wordmark, and name lockup — concept and typefaces settled, see brand-guidelines.adoc; exact icon variation still being finalized.