Skip to content

Conversation

@hcopp
Copy link
Contributor

@hcopp hcopp commented Jan 13, 2026

What changed? Why?

This PR adds a new chart component, Legend.

It also adds support for hideBeaconLabels prop to Scrubber and BarComponent to BarChart.

UI changes

Mobile

Web

image image image image image image

Testing

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
Collaborator

cb-heimdall commented Jan 13, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/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 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

@hcopp hcopp self-assigned this Jan 13, 2026
@hcopp hcopp requested a review from cb-ekuersch January 13, 2026 22:13
@hcopp hcopp marked this pull request as ready for review January 13, 2026 22:16
</Text>
) : (
label
)}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am waiting to hear back from design regarding if we should add aria-hidden to the default legend item or not, since users may expect to only hear the name of each series when scrubbing.

},
]}
yAxis={{ domain: { min: -domainLimit, max: domainLimit } }}
aria-hidden="true"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This example and above were interfering with screen reader since they automatically update.

key: 'Combobox',
getComponent: () =>
require('@coinbase/cds-mobile/alpha/combobox/__stories__/Combobox.stories').default,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was added when running the sync script

originY={effectiveOriginY}
roundBottom={roundBottom}
roundTop={roundTop}
seriesId={seriesId}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This provides support for custom bar components

}
```
You can use `hideBeaconLabels` to hide beacon labels, while still being able to provide a label for a series.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a nice benefit of legend, teams can still label lines without the beacon labels

@hcopp
Copy link
Contributor Author

hcopp commented Jan 20, 2026

Tested this with design & confirmed accessibility, made a few changes. It is ready for review again :)

* - A React element renders that element as the legend
* - `false` or omitted hides the legend
*/
legend?: boolean | React.ReactElement<LegendProps>;
Copy link
Contributor

Choose a reason for hiding this comment

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

traditionally we have accepted React components instead of JSX elements - what is the rationale here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Initially I was accepting just a boolean here, and then I saw the need to customize legend without wanting to add too many extra fields to CartesianChart.

I then for a bit was cloning the component passed in to add an additional field but realized this wasn't a great practice so I updated this.

Now it will be boolean | React.ReactNode. However I agree that this still might not be correct. Across the app we accept ReactNode in many places.

SlideButton accepts startUncheckedNode and endCheckedNode and it also accepts SlideButtonHandleComponent and SlideButtonBackgroundComponent.

Select accepts ReactNode for label and also accepts SelectControlComponent.

In these cases we have been accepting the components in cases where we need to pass props, however for here we don't need to since the user can use context. But in the cases where we accept a reactnode the requests for the user are much simpler, maybe an Icon or Text component. Neither of these cases fully match Legend, this feel a little more uncharted.

If we switch to component we could support showLegend and LegendComponent. If we keep reactnode we can do legend and keep with boolean or reactnode.

Copy link
Contributor

Choose a reason for hiding this comment

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

@hcopp so the user would need to render something that consumes the chart context under the hood? It almost feels simpler to support the LegendComponent and pass props that way. In that case I would suggest inverting the show to hide: hideLegend

Again, I trust your judgement on this. I think what rang the bell for me was LegendProps - in its current form the customization already forces them to write a component that mirrors our legend interface which could in theory just be passed as a custom component

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The prop is now legend?: boolean | React.ReactNode so we don't need to worry about LegendProps, that was leftover from earlier testing of mine when I was going to see if there was a benefit to cloning the node, and I have dropped that logic entirely.

Just spoke with Adrien. I think this current path is the simplest given the problem doesn't fully sit on either side of our traditional sub/default components or react node.

Someone can make their own Legend fully though and pass in via ReactNode now though!

* Accessibility label for the legend group.
* @default 'Legend'
*/
legendAccessibilityLabel?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

when and how do we determine what props we should surface on the parent component? One could argue that the label can be set by providing their own variaiton of the default legend component if the really needed to

Copy link
Contributor Author

@hcopp hcopp Jan 21, 2026

Choose a reason for hiding this comment

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

I followed adding this based on Select Alpha's controlAccessibilityLabel. In my testing with design, they expressed the desire for this to be set when using a legend and as such not including this on the base component would require users to always pass in a react node to be compliant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants