adding-props
Skill131 starsChanged 8 days agoApache-2.0View on GitHub
Adds a new prop to an existing chart mark or component (e.g. blend on Scatter, labelLimit on Axis, onMouseOver on Bar), covering types, spec builder, defaults, tests, stories, S2 dashboards, and docs. Use when adding or changing a prop.
In this repository12 other files it gives its agents.
- AGENTS.mdAGENTS.md
- adding-chart-marksSkill
- adding-child-componentsSkill
- chart-featuresSkill
- comparing-time-periodsSkill
- filing-issuesSkill
- fixing-bugsSkill
- implementing-milestonesSkill
- s2-design-tokensSkill
- summarizing-bugsSkill
- writing-rfcsSkill
- writing-s2-docs-pagesSkill
The file
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 3 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use this skill when adding a new prop to an existing chart mark or component (e.g., blend on Scatter, labelLimit on Axis, onMouseOver on …
- Very specific: line 10
Read architecture/encoding-and-props.md (encoding conventions, and — if the prop is a callback — callback/boolean-flag conventions).
- Very specific: line 16
If this work has a GitHub issue (gh issue view <number> --repo adobe/react-spectrum-charts) or comes from an accepted RFC in rfcs/, read it …
- Distinctive: line 22
If the implementation diverges from the issue, update the issue body.
- Very specific: line 38
When to use conditional spreading: If the prop's absence should mean "let Vega use its own default" (not inject undefined), use ...(prop …
- Very specific: line 69
Add the field to <Mark>Options:
- Very specific: line 78
If it has a runtime default, add the key to <Mark>OptionsWithDefaults:
- Very specific: line 85
If it has no default (absence means "skip the behavior"), do NOT add to OptionsWithDefaults.
- Distinctive: line 105
Add the field at its default value to the default fixture object:
- Very specific: line 117
Test the affected builder function directly with the new prop.
- Distinctive: line 128
Add the prop to the destructure list with its default (for Storybook discovery):
- Very specific: line 155
Read the existing story file first to understand ordering and grouping.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
react-spectrum-charts: Adding a New Prop to an Existing Component
Use this skill when adding a new prop to an existing chart mark or component (e.g., blend on Scatter, labelLimit on Axis, onMouseOver on Bar).
Read architecture/encoding-and-props.md (encoding conventions, and — if the prop is a callback — callback/boolean-flag conventions). If it exists in S2, also read architecture/s2-parity.md.
Step 0: Check for a Linked Issue or Accepted RFC
If this work has a GitHub issue (gh issue view <number> --repo adobe/react-spectrum-charts)
or comes from an accepted RFC in rfcs/, read it and treat it as the requirements: the
design, defaults, cross-cutting concerns, and implementation plan. Re-verify file and
function references against the current code; the issue may have been written before other
changes landed.
If the implementation diverges from the issue, update the issue body. If it diverges from an accepted RFC's design, stop and tell the user. If neither exists, proceed as below.
The PR description says Closes #<number> so merging closes the issue.
Prop Types and Their Implications
Understanding which type of prop you're adding determines which files need to change.
Type 1: Plain Value Prop (Most Common)
A simple value (boolean, number, string, enum) with no special React boundary handling. Examples: hasSquareCorners, blend, labelLimit, hideValue, padding.
These pass through the ...markProps spread automatically. The adapter doesn't change. The only distinction is whether the prop has a runtime default (goes in OptionsWithDefaults) or is truly optional with no fallback (does not go in OptionsWithDefaults).
When to use conditional spreading: If the prop's absence should mean "let Vega use its own default" (not inject undefined), use ...(prop !== undefined && { prop }) in the spec builder utility rather than always including the field.
Type 2: Callback / Event Prop
A function prop (onClick, onMouseOver, onMouseOut, onContextMenu). These never enter the Vega spec as functions. They get converted to boolean flags in the adapter, which gate the creation of voronoi/hover marks in the spec builder.
The flag mapping is mark-specific — check the existing adapter before writing new logic. For Line, click and hover use separate flags:
hasOnClick: Boolean(onClick)— gates click-specific behaviorhasMouseInteraction: Boolean(onMouseOut || onMouseOver)— gates hover-specific behavior
Other marks may combine them differently. Always read the adapter pattern before assuming hasOnClick covers all callbacks.
Callback props live in <Mark>Props only — they don't exist in <Mark>Options. They must be added to the Omit list in <Mark>Props if they share a name with an Options field (which they usually don't) or simply added as new fields.
Changes to the Vega spec are minimal or none — the spec builder already emits hover/click marks when isInteractive() returns true. The main change is wiring the React callback to fire when the corresponding Vega event happens, which lives in useMarkMouseInputDetails.tsx or useNewChartView.tsx.
Type 3: Encoding Prop
A prop that changes the Vega encode block of a mark — fill, stroke, opacity, shape, size, blend mode. Examples: stroke, blend, opacity, lineOpacity.
These flow into the mark utility function (e.g., getScatterMark) and modify encode.enter or encode.update. The main implementation work is in the mark utility, not the spec builder orchestrator.
Type 4: Scale / Data / Signal Prop
A prop that changes how data is structured, how scales are built, or how interaction signals work. Examples: metricAxis, tickCountLimit, hidePartialWindows, highlightBy.
These require changes to addData(), setScales(), or addSignals() in the spec builder. They may also require changes to axisSpecBuilder.ts, scaleSpecBuilder.ts, or signalSpecBuilder.ts.
Implementation Steps
Step 1: Types (vega-spec-builder/src/types/marks/<mark>Spec.types.ts)
Add the field to <Mark>Options:
/**
* JSDoc description of the prop.
* @default false
*/
myNewProp?: boolean;If it has a runtime default, add the key to <Mark>OptionsWithDefaults:
type LineOptionsWithDefaults =
| 'existingField'
| 'myNewProp'; // add hereIf it has no default (absence means "skip the behavior"), do NOT add to OptionsWithDefaults. Use conditional spreading when consuming it.
Step 2: Spec Builder (vega-spec-builder/src/line/lineSpecBuilder.ts)
Destructure the new field with its default in the addLine produce callback:
export const addLine = produce<ScSpec, [...]>((spec, {
myNewProp = false,
// ...existing fields
}) => {
const lineOptions: LineSpecOptions = {
myNewProp,
// ...
};Then implement the behavior in whichever of addData, addSignals, setScales, or addLineMarks is appropriate.
Step 3: Test Fixture (vega-spec-builder/src/<mark>/<mark>TestUtils.ts)
Add the field at its default value to the default fixture object:
export const defaultLineOptions: LineSpecOptions = {
// ...existing fields
myNewProp: false, // add with default value
};This is critical — any toStrictEqual test that uses this fixture will fail if the field is missing.
Step 4: Unit Tests
Test the affected builder function directly with the new prop. Use toHaveProperty for Vega encode assertions — never direct property access (Vega uses ProductionRule<T> union types):
// Correct
expect(mark.encode?.enter?.fill).toHaveProperty('value', 'blue');
// Wrong — TypeScript error
expect(mark.encode?.enter?.fill?.value).toBe('blue');Step 5: React Component (react-spectrum-charts/src/components/<Mark>/<Mark>.tsx)
Add the prop to the destructure list with its default (for Storybook discovery):
const Line: FC<LineProps> = ({
myNewProp = false,
// ...
}) => { return null; };Step 6: React Props Type — Only If Needed
Edit react-spectrum-charts/src/types/marks/<mark>.types.ts only if:
- The prop is a callback that needs to be added to
<Mark>Props(not in<Mark>Options) - The prop type differs between the React boundary and the spec builder (rare)
For plain value props, <Mark>Props already inherits the field from <Mark>Options via the Omit pattern. No change needed.
Step 7: Adapter — Only If Needed
Edit react-spectrum-charts/src/rscToSbAdapter/<mark>Adapter.ts only if:
- A callback needs conversion to a boolean flag
- Conditional spreading is needed (undefined vs absent distinction)
- The prop requires transformation
For props that pass through unchanged, the adapter's ...markProps spread handles them automatically.
Step 8: Storybook Story
Read the existing story file first to understand ordering and grouping. Add the story in a logical position relative to similar stories. Use bindWithProps and set only args that differ from defaultArgs.
Step 9: S2 Parity
Check whether the mark exists in S2 (packages/vega-spec-builder-s2/src/ and packages/react-spectrum-charts-s2/src/). If it does, apply the same changes. S2 files mirror S1 structurally but use S2-specific imports. S2 does not have Venn, does not have the s2 boolean prop, and some marks have intentional behavioral differences (e.g., staticPoint in S2 only supports true, not 'hollow'/'solid').
Step 10: Variation Dashboards (S2)
For S2 props, add or adjust a variation in the mark's dashboard and map the prop in its coverage map. If the prop is on a sibling (Axis, Legend, Chart), update every dashboard where it interacts with the chart. See AGENTS.md's "Variation Dashboards".
Then update the component's packages/docs-s2 page: add the prop to a playground or section, and make sure its TSDoc states what it does and its default (see writing-s2-docs-pages, Step 9).
Checklist by Prop Type
Plain value prop with default:
<mark>Spec.types.ts— add to Options + OptionsWithDefaults<mark>SpecBuilder.ts— destructure with default, add to options object, implement behavior<mark>TestUtils.ts— add to fixture at default value- Unit test for the affected utility
<Mark>.tsx— add to destructure with default- Story
- S2 parity
- S2 variation dashboard variation + coverage entry
- S2 docs page — prop covered in a playground or section
Plain value prop without default (optional behavior):
- Same as above, but omit
OptionsWithDefaultsentry - Use conditional spreading in the utility:
...(prop !== undefined && { prop })
Callback/event prop:
<mark>.types.ts— add to<Mark>PropswithMarkCallbacktype<mark>Adapter.ts— destructure, compute boolean flag<mark>Adapter.test.ts— test flag computationuseMarkMouseInputDetails.tsxoruseNewChartView.tsx— wire the React callback- Integration test exercising the callback
- Story with interactive demo
- No vega-spec-builder changes typically needed
Common Mistakes to Avoid
Missing fixture update — Every toStrictEqual assertion that uses the fixture object will fail if the new field is absent. Update the fixture first.
undefined leaking into the Vega spec — Spreading { prop } when prop is undefined injects prop: undefined into the Vega spec, which may override Vega's own internal defaults. When the distinction matters, use ...(prop !== undefined && { prop }).
Callback converted to flag incorrectly — Marks split callbacks into separate boolean flags. Line uses hasOnClick: Boolean(onClick) and a separate hasMouseInteraction: Boolean(onMouseOut || onMouseOver). Do not assume one flag covers all callbacks — always read the existing adapter for the mark you are modifying before adding new flag logic.
Failing TypeScript but not failing tests — yarn test does not type-check. Run yarn verify (includes tsc) once at task completion, before pushing (see AGENTS.md's Test Completeness Checklist) — not proactively after every file.
Type literals widened with | string — When a prop takes a fixed set of values, define it as a string literal union ('left' | 'right' | 'center'). Do not add | string to widen it — restrict to known valid values only. ESLint and TypeScript will not catch this, but SonarQube will flag it as a code smell.
Multi-line JSDoc/comments narrating the change — New or modified functions get at most a one-line JSDoc (description + @param/@returns), matching the length of sibling functions in the same file. Do not add paragraphs explaining why the prop was added, what it fixes, or what was investigated — that belongs in the PR description, never the code. See AGENTS.md's Code Style section for a worked example.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

