Skip to content

feat: Color pairing tool#524

Merged
sverg-cb merged 10 commits intocoinbase:masterfrom
nicoledbelcher:nicole/colorpicker
Apr 9, 2026
Merged

feat: Color pairing tool#524
sverg-cb merged 10 commits intocoinbase:masterfrom
nicoledbelcher:nicole/colorpicker

Conversation

@nicoledbelcher
Copy link
Copy Markdown
Contributor

@nicoledbelcher nicoledbelcher commented Mar 20, 2026

What changed? Why?

Added a new sidebar to the docs site called extras and created a new color pairing tool inside it.
What it is
The Color Pairing Tool is an internal design utility built into the CDS documentation site. It helps designers and engineers find the closest CDS spectrum primitives for any color and automatically generates accessible, theme-aware color pairings that work across light and dark modes.

How it works

  1. Color Input (two methods)

Image upload — Users can upload up to 10 images (PNG, JPG, or WebP) via file picker or drag-and-drop. The tool extracts dominant colors from each image using a k-means clustering algorithm run client-side on an .
Manual hex entry — Users can type one or more comma-separated hex codes directly into a text input.
2. Token Matching

For each extracted or entered color, the tool runs a hue-aware matching algorithm against the full CDS spectrum (both light and dark). This isn't a simple "nearest Euclidean distance" match — it's purpose-built for CDS tokens and factors in perceptual hue so the matched primitive feels like a natural fit, not just the mathematically closest one.

  1. Contrast & Accessibility

Once a primary background token is matched, the tool:

Selects a foreground text color that meets WCAG AA contrast requirements (minimum 4.5:1 for normal text).
Computes a secondary/complementary token pairing.
Offers a high-contrast mode toggle that enforces even stricter contrast ratios.
Displays live WCAG contrast ratios (AA Normal, AA Large, AAA) for every pairing.
4. Component Preview

The results feed into a Component Playground that renders real CDS components — Card, Button, MessagingCard, LineChart, ProgressBar, and more — using the matched token pairings. This gives an immediate preview of how the colors will look in a production UI across both light and dark themes.

  1. Resampling

For image uploads, users can drag a hotspot around the image to resample the dominant color from a different region, which recalculates the token match and all downstream pairings in real time.

  1. Export

Results can be exported as structured JSON containing the matched tokens, hex values, text colors, and button state variants for both light and dark modes — ready to hand off to engineering.

Key technical details
Runs entirely client-side — no server calls. All color math, image processing, and token matching happen in the browser.
Uses CDS design tokens as the source of truth (imported from tokens.ts), so matches always stay in sync with the design system.
Theme-aware throughout — every visual (checkerboards, previews, component playground) adapts to the user's current light/dark mode setting.

UI changes

Screenshot 2026-03-27 at 9 48 52 AM Screenshot 2026-03-27 at 9 49 28 AM Screenshot 2026-03-27 at 9 49 19 AM

Multiple outputs carousel:
Screenshot 2026-03-27 at 11 14 15 AM

Testing

You should test selecting a color from the color picker without uploading an image, and then test uploading one or multiple images. Users can only upload, PNG, JGP and WEBP files, no video/animated gif files allowed.

How has it been tested?

  • Unit tests
  • Interaction tests
  • Pseudo State tests
  • Manual - Web
  • Manual - Android (Emulator / Device)
  • Manual - iOS (Emulator / Device)

Testing instructions

Illustrations/Icons Checklist

Required if this PR changes files under packages/illustrations/** or packages/icons/**

  • verified visreg changes with Terran (include link to visreg run/approval)
  • all illustration/icons names have been reviewed by Dom and/or Terran

Change management

type=routine
risk=low
impact=sev5

automerge=false

@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Mar 20, 2026

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS ✅ See below

CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 1/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

Comment thread .cursor/rules/cds.mdc Outdated
Comment thread .cursor/mcp.json Outdated
Comment thread apps/cds-finder/server/index.ts Outdated
Comment thread apps/docs/docs/extras/color-pairing-tool.mdx
Comment thread apps/vite-app/src/color-pairing/ColorPicker.module.css Outdated
Comment thread package.json Outdated
Comment thread guidelines/Untitled Outdated
Comment thread libs/docusaurus-plugin-docgen/src/scripts/docgenRunner.ts
Comment thread guidelines/overview-setup.md Outdated
Copy link
Copy Markdown
Contributor

@hcopp hcopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the idea! Hopefully this can help you talk with Cursor about how to clean things up.

Also if you could get sample screenshots, description of your intended changes, and a link to the figma in the PR description that would be very helpful!

Comment thread apps/docs/src/components/page/Metadata/MetadataLinks.tsx
Comment thread apps/docs/static/img/campaignCardBanners/color-pairing-tool_dark.png Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/ColorPicker.tsx
Comment thread apps/docs/src/components/page/ColorPairingTool/ComponentPlayground.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/ComponentPlayground.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/ContrastPanel.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/HotspotImagePreview.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/ResultCard.module.css Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/sharedStyles.ts Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/state.ts Outdated
@hcopp
Copy link
Copy Markdown
Contributor

hcopp commented Mar 27, 2026

@nicoledbelcher are you able to fill out the PR description? This is done in GitHub UI manually. You can look at other PRs to see what we normally do to fill it out.

image

Also your commits are unverified, you should follow https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account. This is a requirement to merge in code.

Lastly, can you pull in the latest from master? Cursor can help with this.

Comment thread apps/docs/src/components/page/ColorPairingTool/tokens.ts
Comment thread apps/docs/src/components/page/ColorPairingTool/index.tsx Outdated
Comment thread apps/docs/sidebars.ts
Comment thread apps/docs/src/components/page/Metadata/MetadataLinks.tsx
Comment thread apps/docs/src/components/page/ContentHeader/index.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/WcagBadge.tsx Outdated
@nicoledbelcher nicoledbelcher changed the title Nicole/colorpicker feat: Color pairing tool Mar 27, 2026
Comment thread apps/docs/src/components/page/Metadata/MetadataLinks.tsx
Comment thread apps/docs/src/components/page/Metadata/MetadataLinks.tsx Outdated
Comment thread apps/docs/src/components/page/Metadata/MetadataLinks.tsx
Comment thread apps/docs/src/components/page/ContentHeader/index.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/tokens.ts Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/PlaygroundContent.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/PlaygroundContent.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/PlaygroundContent.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/PlaygroundContent.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/PlaygroundContent.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/index.tsx Outdated
Comment thread apps/docs/src/components/page/ColorPairingTool/types.ts Outdated
Copy link
Copy Markdown
Contributor

@hcopp hcopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you have have merge issues and now have all our recent changes here in your history...

I'm not sure what we can easily do to solve this, I'll let you know if we find anything.

Comment thread .claude/skills/eslint-plugin-custom-rule/SKILL.md
…tching

Adds a Color Pairing Tool to the docs site that extracts colors from uploaded
images (or manual hex input) and maps them to CDS spectrum tokens with WCAG
contrast compliance. Includes drag-and-drop upload, k-means color extraction,
hue-aware token matching, light/dark mode previews, and a component playground.

Made-with: Cursor
… responsive props

Replace mobile layout overrides that relied on CSS `!important` to beat
CDS inline styles with native CDS responsive props (`base`/`tablet`
breakpoints) on the layout components themselves.

Converted:
- ResultCard topRow/topRowLeft/topRowDivider → responsive flexDirection,
  width, height, minHeight, and display props; removed CSS module import
- ComponentPlayground playgroundHeader → responsive flexDirection,
  alignItems, gap props; removed CSS module import
- PlaygroundContent cardsRow/cardBox/ctaLabel → responsive flexDirection,
  height, and Box display props for CTA label toggle; removed CSS module
  import
- index.tsx startOverLabel → Box with responsive display prop

Kept as CSS (not converted):
- `.toolbarRow` and `.carouselCenter` in ColorPairingTool.module.css —
  these use `position: absolute` with `transform: translateX(-50%)` to
  center the pagination carousel between the Start Over and Export
  buttons on mobile. This positional trick cannot be expressed with CDS
  layout props and is better left as targeted CSS.
- `.blurredBg`, `.hotspotWrap`, `.hotspotDot`, `.hotspotLabel`,
  `.previewImg`, `.imageSection` in ResultCard.module.css — these are
  custom visual effects (blur overlay, drag-to-resample hotspot UI) that
  are inherently CSS-driven and not representable as CDS component props.

Made-with: Cursor
Restore !important mobile overrides in ResultCard.module.css since CDS
responsive props use viewport breakpoints which don't account for the
Docusaurus sidebar narrowing the content area on desktop.

Changes:
- Restore mobile stacking overrides for topRow, topRowLeft, topRowDivider,
  playgroundHeader, cardsRow, and cardBox
- Fix toolbar pagination overlap on small screens by using flex-wrap
  instead of absolute positioning — carousel drops to its own centered
  row on mobile
- Show "Start over" label on all screen sizes
- Make the preview Button compact
- Add fixed 200px height to cardBox on mobile for consistent card sizing
- Remove bottom padding on topRowLeft on mobile to reduce gap
- Convert topRowLeft padding from inline style to CDS spacing prop
- Add preserveAspectRatio="xMidYMid slice" to checkerboard SVG for
  proper scaling in the media card
- Delete ColorPairingTool.module.css toolbar absolute positioning rules,
  replace with flex-wrap approach

Made-with: Cursor
@nicoledbelcher nicoledbelcher requested a review from hcopp April 2, 2026 23:13
@hcopp hcopp marked this pull request as ready for review April 3, 2026 12:58
Comment thread apps/docs/src/components/page/ColorPairingTool/colorUtils.ts Outdated
- Remove spectrum re-exports from colorUtils.ts; all consumers now use
  useTheme() to access theme.lightSpectrum/theme.darkSpectrum directly
- Update findBestDarkToken and processImageFile to accept spectrum as
  parameters instead of importing module-level constants
- Delete ColorPairingTool.module.css; toolbar pagination uses inline
  flex-wrap + order instead of absolute positioning CSS
- Simplify WcagBadge to use Tag's built-in endIcon and endIconActive
  props instead of manually composing HStack/Text/Icon

Made-with: Cursor
@nicoledbelcher nicoledbelcher requested a review from hcopp April 3, 2026 16:34
nicoledbelcher and others added 3 commits April 3, 2026 10:30
…sive props

Replace every !important mobile override in ResultCard.module.css with
CDS responsive props (base/tablet/desktop) on the layout components.

CDS breakpoints are ranges (tablet: 768-1279px, desktop: 1280px+), so
both tablet and desktop values must be specified to avoid falling back
to the base (phone) layout at wide viewports.

- ResultCard: topRow, topRowLeft, topRowDivider → responsive flexDirection,
  width, height, minHeight, paddingBottom, display
- ComponentPlayground: playgroundHeader → responsive flexDirection,
  alignItems, gap
- PlaygroundContent: cardsRow, cardBox → responsive flexDirection, height,
  width
- ContrastPanel: remove stale className refs and CSS module import
- Delete the entire !important media query block from ResultCard.module.css

Made-with: Cursor
- Remove closest primitive card and matched-to-primitive text, show only input color swatch
- Add vertical divider between input swatch and contrast panels
- Fix all lint warnings: prop sorting, accessibility labels, import ordering
- Fix type errors: cast spectrum tokens, handle nullable imgSrc, fix margin type

Made-with: Cursor
Made-with: Cursor
Copy link
Copy Markdown
Contributor

@sverg-cb sverg-cb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end of an era

@sverg-cb sverg-cb merged commit 1502a1f into coinbase:master Apr 9, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment