
Cds Code
- 41 installs
- 493 repo stars
- Updated July 31, 2026
- coinbase/cds
Produces Coinbase Design System (CDS) React and React Native UI code using CDS components, style props, and design tokens instead of raw values.
About
Detects the React vs React Native platform, selects appropriate CDS components, and writes UI using CDS style props and validated import paths. A developer uses it whenever creating or updating CDS frontend code.
- Uses style props and design tokens over raw hex/pixel values
- Validates every import against the discovery script export list
Cds Code by the numbers
- 41 all-time installs (skills.sh)
- Ranked #1,366 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/coinbase/cds --skill cds-codeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 41 |
|---|---|
| repo stars | ★ 493 |
| Last updated | July 31, 2026 |
| Repository | coinbase/cds ↗ |
What it does
Produces Coinbase Design System (CDS) React and React Native UI code using CDS components, style props, and design tokens instead of raw values.
Files
CDS Code Writing Skill
Contents
1. Part 1: Initialization | Follow these steps once per session, before you write any code 2. Part 2: Workflow | Follow these steps for all frontend coding tasks
Part 1: Initialization
Perform the following operations only once per session, after the skill is activated.
Prepare CDS documentation
For any CDS documentation needs, you will need to use either of the following tools. If neither are available you may let the user know but still continue on with the task as documentation is helpful but not required.
- Activate the
cds-docsskill OR... - If the
cds-docsskill is not configured, try calling the CDS MCP serverlist-cds-routestool.
Environment Detection
You must determine if you are operating in a React or React Native project before you write any code.
1. Discover installed CDS packages and runtime
Run the bash discovery script: scripts/discover-cds-packages.sh
This will gve you:
- The
CDS Runtime(webormobile) - use this value as theplatformargument for the CDS MCP server - Every installed CDS package: its name, version, and valid export subpaths - these import paths are the ONLY ALLOWED PATHS for importing from CDS packages.
If you are unable to run the bash script, you can likely infer the platform by inspecting the project's source code.
2. Read the platform-specific styling and themeing documentation:
getting-started/stylinggetting-started/theming
Part 2: Workflow
For all frontend coding tasks, you must follow these steps.
YOU MUST perform steps 1 and 2 before writing any code!
Step 1: Identify the appropriate components
Use guidelines/components.md to help identify the appropriate CDS components for the task. The guidelines file will cover most use cases, but you may optionally browse the CDS docs for the full list of supported CDS components.
If you decide your task will require icons (Icon or IconButton) or illustrations (SpotSquare, Pictogram, HeroSquare, etc.) please read the corresponding guidelines files for more details.
| Icons | Illustrations |
|---|---|
guidelines/icons.md | guidelines/illustrations.md |
If the task involves icons, also follow guidelines/icons.md and use scripts/discover-cds-icons.mjs to search icon names. If the task involves illustrations, also follow guidelines/illustrations.md and use scripts/discover-cds-illustrations.mjs to search illustration names.
If no CDS component fits your use case, you may fall back to the following options in this order of priority:
1. use a custom React component from the project's codebase 2. build your own custom React component 3. use the native platform's JSX elements for bespoke UI
IMPORTANT: Always inform the user which CDS components you are planning to use before moving on to Step 2.
Step 2: Optionally read component docs
For any CDS component you plan to use, retrieve and read their documentation (see Part 1 for more details on docs setup).
Step 3: Execute the task (writing frontend code)
Now create or update the UI with proper CDS components and usage.
Most CDS component implement an API that allows you to apply the CDS design tokens, we call these 'style props'. Prefer setting these style props for styling components over setting custom style via inline styles or CSS.
Why this matters: When you set font, color, textAlign, or other typography properties through style instead of props, the component loses its connection to the CDS theme. For example, setting fontSize and fontWeight via style without a font prop means the CDS font family never applies -- the text falls back to inherit and may render in the wrong typeface.
You should check a component's props table in their CDS docs page to verify what props are available.
Example misuse of custom styles and their style props alternatives:
Instead of style | Use the prop |
|---|---|
style={{ color: "var(--color-fgMuted)" }} | color="fgMuted" |
style={{ fontSize: 12, fontWeight: 500, lineHeight: "16px" }} | font="caption" (or the matching CDS font token) |
style={{ textAlign: "center" }} | textAlign="center" |
style={{ textTransform: "uppercase" }} | textTransform="uppercase" |
style={{ display: "flex", flexDirection: "column" }} | Use VStack, or flexDirection="column" on Box |
style={{ gap: 8 }} | gap={1} |
style={{ padding: 16 }} | padding={2} |
style={{ backgroundColor: "..." }} | background="bgAlternate" (or semantic token) |
If you need to further customize the style of a rendered CDS component or a specific style is not support via style props, you may reference: guidelines/customizing-styles.md.
Step 4: Validate changes
Your task will be complete if:
1. You performed initialization steps in Part 1 2. You examined the user's request and identified specific CDS components to use 3. Your changes DO NOT include any raw rgb/hex/etc color values 4. Your changes DO NOT use any raw pixel values for spacing, border radius, etc. 5. You changes use style props (e.g. font, color, textAlign, textTransform, padding, gap) instead of customization via style or with CSS. 6. All import paths are valid CDS package exports (see section below) 7. Any project linting/typechecking tasks are passing
Validating import paths
This is critical. Do not guess or memorize CDS import paths. The discovery script output is the source of truth (see Part 1 for details).
Before writing or returning any CDS import, verify it against the export list from setup:
1. Find the CDS package for the target platform in the discovery script output. 2. Confirm the subpath you want to import is listed as a valid export. 3. If the subpath is not listed, it does not exist -- pick the closest valid export instead.
The package name may vary between projects. Different repos may install CDS under different scopes. Always use the package name reported by the discovery script, not a hardcoded scope. If the project already has CDS imports in existing code, match whatever scope those files use.
Common mistakes to avoid:
- Inventing deep subpaths like
<pkg>/layout/Boxor<pkg>/buttons/Buttonwhen the actual export is<pkg>/layoutor<pkg>/buttons. - Guessing a package scope when the project uses a different one.
- Assuming that the CDS docs examples use the same package name as the target project -- they may differ.
{
"skill_name": "cds-code",
"evals": [
{
"id": 1,
"prompt": "Build a profile card for our home page that shows the user's avatar, display name, and email address in muted font. Give the container subtle rounded corners. Below it, show three settings-style menu rows for 'Account settings', 'Notifications', and 'Sign out' — each with a right-side arrow.",
"expected_output": "A React component composing Avatar, VStack/HStack for layout, and ListCell with accessory=\"arrow\" for each settings row. All spacing and colors use CDS tokens — no raw pixel values or hex colors. Import paths are valid CDS exports.",
"files": [],
"expectations": [
"Uses Avatar for the user photo",
"Uses ListCell with accessory=\"arrow\" for the three settings rows",
"Uses fgMuted (not foregroundMuted or bespoke color) for the email address text",
"Uses the generic Text component with the font prop, not derivitive Text components (e.g. TextHeadline, TextLabel1, etc.)",
"Uses the borderRadius prop for the rounded container cornders and uses a design token for the value (no pixels/percentages)",
"No hardcoded hex, rgb, or raw color values anywhere in the output code",
"No raw pixel values used for spacing or border radius (e.g., no '16px', '8px' as literal strings)",
"Uses VStack or HStack for layout composition",
"All CDS import paths are valid subpath exports (no invented deep paths like /layout/Box)",
"Before writing any code, the agent explicitly states which CDS components it plans to use"
]
},
{
"id": 2,
"prompt": "I need a 'Create team' modal with a text input for the team name, a dropdown to pick the team size (Small / Medium / Large), and Save / Cancel buttons in the footer.",
"expected_output": "A React component using Modal, ModalHeader, ModalBody, ModalFooter, TextInput, Select (from alpha subpath), and Button. The footer has a primary Save button and a secondary Cancel button. No style={{ ... }} used for properties that have CDS prop equivalents. Import paths are valid.",
"files": [],
"expectations": [
"Uses Modal, ModalHeader, ModalBody, and ModalFooter to compose the dialog",
"Uses TextInput for the team name field",
"Uses Select (from the alpha subpath) for the team size dropdown with Small/Medium/Large options",
"ModalFooter receives a primary Button (Save) and a secondary variant Button (Cancel)",
"No style prop used for padding, color, gap, or other values that have CDS style prop equivalents",
"Select is imported from the correct alpha subpath (e.g., @coinbase/cds-web/alpha/select or equivalent)",
"No raw pixel values or hex/rgb colors in the output",
"Before writing any code, the agent explicitly states which CDS components it plans to use"
]
},
{
"id": 3,
"prompt": "Build a banner that shows a warning message 'You have unsaved changes' with a 'Save now' link action. Below it, in a container with 2 units of horizontal padding, show a progress bar at 60% completion and a spinner for a secondary loading operation.",
"expected_output": "A React component using Banner with variant=\"warning\", ProgressBar at progress={0.6} or progress={60}, and ProgressCircle with indeterminate. Visualization components are imported from the visualization package. No hex/rgb colors anywhere.",
"files": [],
"expectations": [
"Uses Banner with variant=\"warning\" for the unsaved changes message",
"Renders a container below the banner with padding applied via the paddingX, paddingStart or paddingEnd props (e.g. paddingX={2})",
"Uses ProgressBar with a progress value representing 60% for the determinate progress indicator",
"Uses ProgressCircle with the indeterminate prop for the spinner",
"ProgressBar and ProgressCircle are imported from @coinbase/cds-web/visualizations (the correct subpath), not from a generic top-level import or the separate @coinbase/cds-web-visualization package",
"No hardcoded hex, rgb, or raw color values in the output",
"Before writing any code, the agent explicitly states which CDS components it plans to use"
]
},
{
"id": 4,
"prompt": "Create a left sidebar nav with 3 items: Home, Activity, Settings. Home is currently selected.",
"expected_output": "A React component that uses Icon names home/activity/settings, sets active state only on Home, and uses token-based selected vs unselected color treatment.",
"files": [],
"expectations": [
"Before writing code, the agent identifies Icon (and layout primitives) as planned CDS components",
"Uses the exact icon names home, activity, and settings for the three nav items",
"Applies active state specifically to Home and keeps Activity/Settings inactive",
"Uses navigation-appropriate icon sizing",
"Pairs selected state with token-based color treatment (no bespoke hex/rgb values)",
"No raw pixel or hex/rgb values are used"
]
},
{
"id": 5,
"prompt": "Create a security empty state with a title, supporting text, and an illustration. Show compact and roomy versions of the same visual.",
"expected_output": "A React component that uses SpotIcon name=\"2fa\" with compact 24x24 and roomy 32x32 dimensions plus CDS text/layout primitives.",
"files": [],
"expectations": [
"Uses SpotIcon specifically",
"Uses SpotIcon name 2fa",
"Uses exact SpotIcon dimensions 24x24 and 32x32",
"Uses CDS layout and generic Text primitives",
"Does not use or suggest scaleMultiplier",
"No raw pixel or hex/rgb values are used"
]
}
]
}
CDS component selection guide
For full prop and type details, refer to the CDS component docs and TypeScript definitions; this guide is for _choosing_ components and patterns.
When the user describes a UI need, reach for these first:
| Need | Use |
|---|---|
| Page or section title | Text with font="title1" – font="title4" |
| Body copy | Text with font="body" |
| Muted helper text | Text with font="body" and color="fgMuted" |
| Submit / confirm action | Button variant="primary" |
| Cancel / close action | Button variant="secondary" |
| Destructive action | Button variant="negative" |
| Icon-only action | IconButton |
| Single-line input | TextInput |
| Dropdown | Select |
| Autocomplete / typeahead | Combobox |
| Filter chip with options | SelectChip |
| On/off toggle | Switch |
| Multi-select boxes | Checkbox inside ControlGroup role="group" |
| Mutually exclusive options (≤5) | SegmentedTabs or Radio inside ControlGroup role="radiogroup" |
| Mutually exclusive options (>5) | Select |
| Chip-style tabs | TabbedChips (deep import) |
| Confirm dialog | Modal + ModalFooter |
| Mobile bottom sheet | Tray |
| Anchored interactive panel | Popover |
| Hover hint | Tooltip |
| Transient success/error | useToast().show(...) |
| Inline page-level alert | Banner |
| Inline informational note | Banner variant="informational" or Box with text |
| Settings/menu row | ListCell |
| Tabular data | Table |
| Standard card | ContentCard |
| Card with media | MediaCard |
| In-product messaging card | MessagingCard |
| Data summary / metric card | DataCard |
| Loading placeholder shape | Fallback |
| Indeterminate spinner | <ProgressCircle indeterminate /> |
| Determinate circular progress | ProgressCircle with progress |
| Determinate linear progress | ProgressBar with progress |
| Status pill / label | Tag |
| User photo | Avatar |
Use the detailed sections below only when you need clarification; they intentionally avoid full prop API dumps and focus on when/why to pick a component plus key gotchas.
---
Categories
1. Layout 2. Typography 3. Buttons & Actions 4. Form Inputs 5. Overlays 6. Lists, Tables & Cards 7. Feedback & Status 8. Navigation 9. Media & Decoration 10. Visualization
---
1. Layout
Box
- What it is: Polymorphic layout primitive for flex/grid and simple block layouts.
- Use when: You need one‑off layout or styling that doesn’t fit a more specific component.
- Avoid
marginX="auto"(not supported) — center withjustifyContent="center"on the parent oralignSelf="center"on the child.
VStack / HStack
- What they are: Vertical and horizontal flex stacks — thin wrappers around
Boxwith a fixed flex direction. - Use when: You’re just stacking items with consistent spacing.
- Prefer these over
Boxwhen the intent is purely vertical/horizontal stacking. - They accept all the same layout props as `Box` (padding, gap, background, borderRadius, etc.) — so never wrap
<Box paddingX={2}><VStack>when you can just write<VStack paddingX={2}>.
Divider
- Horizontal or vertical rule to separate content.
Spacer
- Empty box to push siblings apart inside flex layouts.
- Prefer using
gapon the parent; useSpaceronly when you need explicit flexible space.
Responsive props
- Layout props accept responsive objects directly, e.g.:
<VStack gap={{ phone: 1, tablet: 2, desktop: 3 }} />
---
2. Typography
- Use the generic `Text` component with a
fontprop for all text rendering — do not use the derivative shorthand components (TextTitle1,TextTitle2,TextTitle3,TextTitle4,TextHeadline,TextBody,TextLabel1,TextLabel2, etc.). Those are v7 patterns. - The
fontprop accepts CDS type-scale tokens:"title1"–"title4","headline","body","label1","label2","caption", etc. - For muted/secondary text, use
color="fgMuted"(not"foregroundMuted"). Textis polymorphic viaasand supports common text props (color,textAlign,textTransform, etc.).
Example:
<Text font="title3">Display Name</Text>
<Text font="body" color="fgMuted">user@example.com</Text>---
3. Buttons & Actions
Button
- Primary click target for actions.
- Use for: Submits, primary/secondary/negative actions, full‑width CTAs (
block). - Key details:
- Uses
onClick(notonPress). compactis the size control; there is no genericsizeprop.
IconButton
- Icon‑only action.
- Use for: Small, compact icon actions (close, edit, settings, etc.).
- Key details:
- Requires an
accessibilityLabeldescribing the action.
ButtonGroup
- Wrap multiple
Buttons for consistent spacing/alignment within a region (e.g., modal footers, form actions).
---
4. Form Inputs
Focus on which input to pick, not the full event/value API.
TextInput
- Single‑line text field.
- Use for: Freeform text, email, password, numeric, phone, URL, etc.
- For error states, rely on variants + helper text rather than a dedicated
errorTextprop.
Select
import { Select } from "@coinbase/cds-web/alpha/select";
- Dropdown for single or multi selection from a finite option set.
- Use for:
- Mutually exclusive options when there are many values (>5).
- Multi‑select of structured options.
- Uses an array of
{ value, label, disabled? }options rather than JSX children.
Combobox
import { Combobox } from "@coinbase/cds-web/alpha/combobox";
- Autocomplete / typeahead control.
- Use when: The user types to filter a relatively long list of options.
SelectChip
import { SelectChip } from "@coinbase/cds-web/alpha/select-chip";
- Filter chip that opens a dropdown of options.
- Use when: You want a compact, chip‑style filter control (e.g., filtering lists/tables).
Switch
- On/off toggle.
- Use for: Single boolean settings.
Checkbox + ControlGroup
- Use
Checkboxfor independent toggles or lists of multi‑select items. - Group multiple checkboxes inside
ControlGroup role="group"when they belong together.
Radio + ControlGroup
- Use for: Mutually exclusive options where each option is always visible.
- Wrap radios in
ControlGroup role="radiogroup".
SegmentedTabs
- Tab‑style picker for 2–5 mutually exclusive options (a more compact alternative to radios).
DateInput / DatePicker
- Date inputs where the value is an ISO string and selection semantics matter.
- Use when users must pick specific calendar dates, not just arbitrary strings.
Slider
- Numeric range selector or single value on a continuum.
- Use when dragging a handle is more intuitive than typing numbers.
SearchInput
- Preset
TextInputvariant with search icon and clear button. - Use for primary search bars and filter/search fields.
Form composition helpers
Field: Wraps any input with label + helper/error text.FieldLabel,FieldHelperText,FieldErrorText: Low‑level building blocks.- Use these to keep labels and error messaging consistent across custom compositions.
---
5. Overlays
Modal
- Centered overlay dialog.
- Use for: Blocking flows requiring focused attention and explicit dismissal.
- Key details:
- No title/description props directly on
Modal; compose withModalHeader,ModalBody, andModalFooter.
ModalHeader, ModalBody, ModalFooter
ModalHeader: Provides the title and optional description.ModalBody: Scrollable content region.ModalFooter: Primary/secondary actions (expects actualButtonelements, not strings or bareonClickhandlers).
Tray
- Bottom‑sheet style overlay (mobile‑first).
- Use for: Flows that feel lighter than full modals, especially on mobile.
Popover
- Anchored floating panel near a trigger element.
- Use for: Contextual menus or small pieces of interactive content anchored to a control.
- Key detail: Trigger/anchor is the
childrenofPopover, not a separatetriggerprop. Positioning lives incontentPosition.
Tooltip
- Small hover/focus hint attached to a single child element.
- Use for: Short, non‑essential explanations of controls and icons.
useToast (hook)
- Imperative toasts for transient success/error/info feedback.
- Use for: "Saved successfully", "Could not connect", etc.
- Do not render a
<Toast />directly; rely onuseToast().show(...).
---
6. Lists, Tables & Cards
ListCell
- Row primitive for lists, settings, and menus.
- Use for: Settings lists, profile lists, navigation lists, etc.
- Use
mediafor leading visuals andaccessory/accessoryNode/endfor trailing content; see CDS docs for the full API.
Table
- Tabular data presentation.
- Use for: Dense data, sortable columns, and paginated datasets.
Accordion / AccordionItem
- Expand/collapse regions for content that doesn’t need to be visible all at once.
ContentCard (+ ContentCardHeader, ContentCardBody, ContentCardFooter)
- Standard composable card.
- Use wherever you previously used generic
Cardcomponents.
MediaCard
- Card with a prominent media area (e.g., image, illustration).
- Use for content that is visually led (promos, featured content).
MessagingCard
- In‑product messaging card.
- Use for: Upsells, nudges, announcements, and similar messaging blocks.
- Uses a
typeto differentiate visual style (e.g.,'upsell','nudge').
DataCard
import { DataCard } from "@coinbase/cds-web/alpha/data-card";
- Card for summarizing key data points (metrics, balances).
- Use for: Dashboard tiles and summary metrics; always prefer this implementation over legacy
DataCard.
Fallback
- Skeleton / placeholder shape.
- Use for: Loading states where you know the final structure but not the data yet.
---
7. Feedback & Status
Tag
- Small status/label pill (there is no
Badgein CDS). - Use for: Status indicators, small inline labels and tags.
Banner
- Inline page‑level alert.
- Use for: Warnings, promotional messages, informational notes, and errors that sit inline with content.
- Takes a
variant(warning/promotional/informational/error) and optional actions viaLinkcomponents.
ProgressCircle
- Circular progress indicator from
@coinbase/cds-web-visualization. - Use for:
indeterminateloading spinners.- Determinate progress when you want circular visuals instead of bars.
ProgressBar
- Linear determinate progress from
@coinbase/cds-web-visualization. - Use for: Long‑running operations where linear progress is easy to interpret.
---
8. Navigation
Tabs
- Composable tab bar using a
tabsdata array and controlledactiveTab. - Use for: Section switching where each tab corresponds to a different view.
SegmentedTabs
- Higher‑level wrapper around
Tabsfor 2–5 mutually exclusive options. - Use when: You want simple segmented controls.
TabbedChips
import { TabbedChips } from "@coinbase/cds-web/alpha/tabbed-chips/TabbedChips";
- Chip‑styled tabs.
- Use for: Horizontally scrolling chip‑like navigation with overflow handling.
Pagination
- Pagination control.
- Use for: Paged results in tables or content feeds.
Stepper
- Multi‑step progress indicator.
- Use for: Wizards, onboarding flows, and any multi‑step process, vertically or horizontally.
---
9. Media & Decoration
Avatar
Avatar: Single user/entity image with initials fallback.- Use for: Representing people, accounts, or entities.
Icon
- Single glyph icon.
- Use for: Supplementary visual cues; pair with labels for clarity and accessibility.
Image / RemoteImage
- Display bitmap images with control over object fit, size, and border radius.
SpotSquare
- Decorative square illustration, often used inside cards or list rows.
Pictogram
- Decorative pictogram for more illustrative moments.
Logo
- Coinbase wordmark or symbol.
- Use for: Brand marks; pick
variantandcolorthat match the context.
---
10. Visualization
From @coinbase/cds-web-visualization.
Charts
LineChart,BarChart,AreaChart,PieChart.- Use for: Time series, comparisons, distributions, and proportions, respectively.
- Configure via
data, axes, andseriesdefinitions.
Legend
- Standalone legend component to accompany charts.
Customizing styles
Prefer using CDS Design Tokens as values over hardcoded values. Examples:
- On web, prefer
marginTop: 'var(--space-0_5)'overmarginTop: '4px'. - On web, prefer
borderRadius: 'var(--borderRadius-200)'overborderRadius: '8px'. - On mobile, prefer
marginTop: theme.space[0.5]overmarginTop: 4. - On mobile, prefer
borderRadius: theme.borderRadius[200]overborderRadius: 8. - Prefer
<Box background="bgAlternate" padding={2} />over a custom wrapper with hardcoded CSS.
style on Select
import { memo, useState } from 'react';
import { Select } from '@coinbase/cds-web/alpha/select'; // or '@coinbase/cds-mobile/alpha/select'
import { VStack } from '@coinbase/cds-web/layout'; // or '@coinbase/cds-mobile/layout'
const selectOptions = [
{ value: 'option1', label: 'Option 1', description: 'Description' },
{ value: 'option2', label: 'Option 2', description: 'Description' },
{ value: 'option3', label: 'Option 3', description: 'Description' },
];
export const SelectExample = memo(() => {
const [selectValue, setSelectValue] = useState<string | null>(null);
return (
<VStack>
<Select
compact
label="Label"
labelVariant="inside"
onChange={setSelectValue}
options={selectOptions}
placeholder="Select an option"
style={{ flexGrow: 1 }}
value={selectValue}
/>
<Select
label="Label"
onChange={setSelectValue}
options={selectOptions}
placeholder="Select an option"
style={{ flexGrow: 1 }}
value={selectValue}
/>
</VStack>
);
});styles on Select
import { useState } from 'react';
import { Select } from '@coinbase/cds-web/alpha/select'; // or '@coinbase/cds-mobile/alpha/select'
function CustomStylesExample() {
const [value, setValue] = useState('1');
const options = [
{ value: null, label: 'Remove selection' },
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
{ value: '4', label: 'Option 4' },
];
return (
<Select
label="Single select - styles"
onChange={setValue}
options={options}
styles={{
control: {
padding: '20px',
backgroundColor: 'lightgray',
},
controlBlendStyles: {
background: 'coral',
hoveredBackground: 'crimson',
pressedBackground: 'red',
},
optionBlendStyles: {
background: 'lightblue',
hoveredBackground: 'blue',
},
dropdown: {
padding: '20px',
backgroundColor: 'pink',
},
}}
value={value}
/>
);
}styles on ContentCell
import { ContentCell } from '@coinbase/cds-web/cells'; // or '@coinbase/cds-mobile/cells'
import { Avatar } from '@coinbase/cds-web/media'; // or '@coinbase/cds-mobile/media'
<ContentCell
spacingVariant="condensed"
title="Profile Information"
subtitle="Active Status"
media={<Avatar alt="Sneezy" name="Sneezy" size="m" colorScheme="blue" />}
accessory="disclosure"
description="This example demonstrates the use of media (avatar) and an accessory indicator."
styles={{
media: {
paddingTop: 'var(--space-0_5)',
},
}}
/>;Web-only classNames plus styles
classNames is a web pattern. On mobile, use styles only.
import { css } from '@linaria/core';
import { DotCount } from '@coinbase/cds-web/dots';
import { VStack } from '@coinbase/cds-web/layout';
import { useTheme } from '@coinbase/cds-web';
const dotCountContainerCss = css`
border-radius: 4px;
`;
function DotCountStyle() {
const theme = useTheme();
return (
<VStack alignItems="flex-start" gap={1}>
<DotCount
classNames={{
container: dotCountContainerCss,
}}
count={30}
styles={{
container: {
backgroundColor: theme.color.bgPositive,
borderColor: theme.color.fg,
},
}}
/>
</VStack>
);
}styles on layout containers
import { Carousel } from '@coinbase/cds-web/carousel'; // or '@coinbase/cds-mobile/carousel'
<Carousel styles={{ carousel: { gap: 16 } }}>{/* carousel items */}</Carousel>;Notes
- Use the same component patterns across web and mobile when you can, and swap the import paths.
- Web components often support
classNameandclassNames; mobile customization is usuallystyleorstyles. stylesis usually slot-based, so use the documented keys likecontrol,dropdown,media, orcontainer.- If layout props can solve it, prefer those over custom styling. Save
styleandstylesfor exceptions.
Icons
Use this guide when the task needs CDS icons.
Find icons
Use the discovery script (scripts/discover-cds-icons.mjs) to list all availabe icons in the installed version of CDS.
Sample usage:
node skills/cds-code/scripts/discover-cds-icons.mjs <query>node skills/cds-code/scripts/discover-cds-icons.mjs <query> --limit 10node skills/cds-code/scripts/discover-cds-icons.mjs <query> --allnode skills/cds-code/scripts/discover-cds-icons.mjs <query> --project-root /absolute/path/to/app
If you are unable to run the script, retrieve the Icon page from the CDS docs.
Size guidance
- Supported sizes:
xs,s,m,l Icondefault size ismIconButtondefaults toswhencompactandmwhen not compact- Navigation/sidebar icon usage is usually
m - Dense inline usage (for example caret affordances) is commonly
s
Active state guidance
- Use
activeonly for selected/toggled states (for example selected bottom nav item, selected sidebar row, toggledIconButton) - Selected icons are often paired with selected color treatment in the surrounding component state
Illustrations
Use this guide when the task needs CDS illustrations.
Find illustrations
Use the discovery script (scripts/discover-cds-illustrations.mjs) to list all availabe illustrations in the installed version of CDS.
Sample usage:
node skills/cds-code/scripts/discover-cds-illustrations.mjs <query>node skills/cds-code/scripts/discover-cds-illustrations.mjs <query> --limit 12node skills/cds-code/scripts/discover-cds-illustrations.mjs <query> --allnode skills/cds-code/scripts/discover-cds-illustrations.mjs <query> --variant Pictogramnode skills/cds-code/scripts/discover-cds-illustrations.mjs <query> --project-root /absolute/path/to/app
If you are unable to run the script, retrieve the appropriate pages from the CDS docs.
Variants
Components: Pictogram, SpotIcon, SpotSquare, SpotRectangle, HeroSquare
Dimension guidance
Pictogram:48x48(default),64x64SpotIcon:32x32(default),24x24SpotSquare:96x96(default)SpotRectangle:240x120(default)HeroSquare:240x240(default),200x200
Pick the closest supported dimension for the layout instead of inventing new values.
cds-code
Helps your agent write idiomatic Coinbase Design System (CDS) code for React or React Native projects.
We recommend also installing the cds-docs Skill or the CDS MCP server for even better performance!
npx skills add https://github.com/coinbase/cds --skill cds-docsRunning evaluations
Use the skill-creator skill to run the evals.
First install the skill-creator skill if it is not already:
npx skills add https://github.com/anthropics/skills --skill skill-creatorRun evals by prompting your agent:
Use the skill-creator skill to run the evals for the cds-code skill
#!/usr/bin/env node
import { promises as fs } from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const invokedScript =
path.relative(process.cwd(), process.argv[1] ?? '') ||
'skills/cds-code/scripts/discover-cds-icons.mjs';
const usage = `Usage: node ${invokedScript} <query> [--project-root <path>]
Example: node ${invokedScript} shield`;
function parseArgs(argv) {
let query = '';
let projectRoot = '';
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--project-root') {
projectRoot = argv[i + 1] ?? '';
i += 1;
continue;
}
if (!query) query = arg;
}
return { query: query.trim(), projectRoot };
}
async function pathExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function findProjectRoot(startPath = process.cwd()) {
let current = path.resolve(startPath);
while (true) {
const packageJsonPath = path.join(current, 'package.json');
const nodeModulesPath = path.join(current, 'node_modules');
if ((await pathExists(packageJsonPath)) && (await pathExists(nodeModulesPath))) return current;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
return '';
}
async function findScopedCdsPackage(projectRoot, suffix) {
const nodeModulesPath = path.join(projectRoot, 'node_modules');
const scopes = await fs.readdir(nodeModulesPath, { withFileTypes: true });
for (const entry of scopes) {
if (!entry.isDirectory() || !entry.name.startsWith('@')) continue;
const packageJsonPath = path.join(nodeModulesPath, entry.name, suffix, 'package.json');
if (!(await pathExists(packageJsonPath))) continue;
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
if (typeof packageJson.name === 'string' && packageJson.name.endsWith(`/${suffix}`)) {
return packageJson.name;
}
}
return '';
}
async function resolveFromProject(specifier, projectRoot) {
const packageJsonUrl = pathToFileURL(path.join(projectRoot, 'package.json')).href;
if (typeof import.meta.resolve === 'function') {
const resolved = await Promise.resolve(import.meta.resolve(specifier, packageJsonUrl));
return fileURLToPath(resolved);
}
const require = createRequire(packageJsonUrl);
return require.resolve(specifier);
}
async function importFromProject(specifier, projectRoot) {
const resolvedPath = await resolveFromProject(specifier, projectRoot);
return import(pathToFileURL(resolvedPath).href);
}
function iconTagsAndDescriptionHit(descriptionMap, iconName, queryLower) {
const tags = [];
let descKeyHit = false;
for (const [tag, iconList] of Object.entries(descriptionMap)) {
if (!tag || !Array.isArray(iconList) || !iconList.includes(iconName)) continue;
tags.push(tag);
if (tag.toLowerCase().includes(queryLower)) descKeyHit = true;
}
tags.sort((a, b) => a.localeCompare(b));
return { tags, descKeyHit };
}
function formatWithTags(label, tags) {
if (!tags.length) return label;
return `${label} (tags: ${tags.join(', ')})`;
}
function printResults(matches, query) {
if (!matches.length) {
console.log(`No matches for "${query}".`);
process.exitCode = 1;
return;
}
const n = matches.length;
console.log(`Found ${n} ${n === 1 ? 'icon' : 'icons'}:`);
for (const { name, tags } of matches) {
console.log(formatWithTags(name, tags));
}
}
async function main() {
const { query, projectRoot: argProjectRoot } = parseArgs(process.argv.slice(2));
if (!query) {
console.error('Error: missing query.\n' + usage);
process.exitCode = 1;
return;
}
const queryLower = query.toLowerCase();
const projectRoot = argProjectRoot ? path.resolve(argProjectRoot) : await findProjectRoot();
if (!projectRoot) {
console.error('Error: no project root (package.json + node_modules). Use --project-root.');
process.exitCode = 1;
return;
}
const pkg = await findScopedCdsPackage(projectRoot, 'cds-icons');
if (!pkg) {
console.error('Error: @coinbase/cds-icons not found in node_modules.');
process.exitCode = 1;
return;
}
let names;
let descriptionMap;
try {
const namesModule = await importFromProject(`${pkg}/names`, projectRoot);
const dmModule = await importFromProject(`${pkg}/descriptionMap`, projectRoot);
names = namesModule.names ?? namesModule.default;
descriptionMap = dmModule.descriptionMap ?? dmModule.default;
} catch (error) {
console.error(`Error: import failed (${pkg}).`);
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
return;
}
if (!Array.isArray(names) || typeof descriptionMap !== 'object' || descriptionMap === null) {
console.error('Error: unexpected icon data shape.');
process.exitCode = 1;
return;
}
const matches = [];
for (const name of names) {
const nameHit = name.toLowerCase().includes(queryLower);
const { tags, descKeyHit } = iconTagsAndDescriptionHit(descriptionMap, name, queryLower);
if (!nameHit && !descKeyHit) continue;
matches.push({ name, nameHit, tags });
}
matches.sort((a, b) => {
if (a.nameHit !== b.nameHit) return a.nameHit ? -1 : 1;
return a.name.localeCompare(b.name);
});
printResults(matches, query);
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});
#!/usr/bin/env node
import { promises as fs } from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const variants = ['pictogram', 'spotIcon', 'spotSquare', 'spotRectangle', 'heroSquare'];
const variantAliases = {
pictogram: 'pictogram',
spoticon: 'spotIcon',
spotsquare: 'spotSquare',
spotrectangle: 'spotRectangle',
herosquare: 'heroSquare',
};
const invokedScript =
path.relative(process.cwd(), process.argv[1] ?? '') ||
'skills/cds-code/scripts/discover-cds-illustrations.mjs';
const usage = `Usage: node ${invokedScript} <query> [--variant <v>] [--project-root <path>]
Variants: ${variants.join(', ')}
Example: node ${invokedScript} shield`;
function parseArgs(argv) {
let query = '';
let projectRoot = '';
let variant = '';
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--project-root') {
projectRoot = argv[i + 1] ?? '';
i += 1;
continue;
}
if (arg === '--variant') {
variant = argv[i + 1] ?? '';
i += 1;
continue;
}
if (!query) query = arg;
}
return { query: query.trim(), projectRoot, variant };
}
function resolveVariant(variantInput) {
if (!variantInput) return '';
const normalized = variantInput.toLowerCase().replace(/[^a-z0-9]/g, '');
return variantAliases[normalized] ?? '';
}
async function pathExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function findProjectRoot(startPath = process.cwd()) {
let current = path.resolve(startPath);
while (true) {
const packageJsonPath = path.join(current, 'package.json');
const nodeModulesPath = path.join(current, 'node_modules');
if ((await pathExists(packageJsonPath)) && (await pathExists(nodeModulesPath))) return current;
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
return '';
}
async function findScopedCdsPackage(projectRoot, suffix) {
const nodeModulesPath = path.join(projectRoot, 'node_modules');
const scopes = await fs.readdir(nodeModulesPath, { withFileTypes: true });
for (const entry of scopes) {
if (!entry.isDirectory() || !entry.name.startsWith('@')) continue;
const packageJsonPath = path.join(nodeModulesPath, entry.name, suffix, 'package.json');
if (!(await pathExists(packageJsonPath))) continue;
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
if (typeof packageJson.name === 'string' && packageJson.name.endsWith(`/${suffix}`)) {
return packageJson.name;
}
}
return '';
}
async function resolveFromProject(specifier, projectRoot) {
const packageJsonUrl = pathToFileURL(path.join(projectRoot, 'package.json')).href;
if (typeof import.meta.resolve === 'function') {
const resolved = await Promise.resolve(import.meta.resolve(specifier, packageJsonUrl));
return fileURLToPath(resolved);
}
const require = createRequire(packageJsonUrl);
return require.resolve(specifier);
}
async function importFromProject(specifier, projectRoot) {
const resolvedPath = await resolveFromProject(specifier, projectRoot);
return import(pathToFileURL(resolvedPath).href);
}
function pickModuleValue(moduleData) {
return moduleData.default ?? moduleData.names ?? moduleData.descriptionMap ?? null;
}
function iconTagsAndDescriptionHit(descriptionMap, iconName, queryLower) {
const tags = [];
let descKeyHit = false;
for (const [tag, iconList] of Object.entries(descriptionMap)) {
if (!tag || !Array.isArray(iconList) || !iconList.includes(iconName)) continue;
tags.push(tag);
if (tag.toLowerCase().includes(queryLower)) descKeyHit = true;
}
tags.sort((a, b) => a.localeCompare(b));
return { tags, descKeyHit };
}
function formatWithTags(label, tags) {
if (!tags.length) return label;
return `${label} (tags: ${tags.join(', ')})`;
}
async function loadVariantData(cdsPackage, projectRoot, variant) {
const namesModule = await importFromProject(
`${cdsPackage}/__generated__/${variant}/data/names`,
projectRoot,
);
const dmModule = await importFromProject(
`${cdsPackage}/__generated__/${variant}/data/descriptionMap`,
projectRoot,
);
const names = pickModuleValue(namesModule);
const descriptionMap = pickModuleValue(dmModule);
if (!Array.isArray(names) || typeof descriptionMap !== 'object' || descriptionMap === null) {
throw new Error(`Bad data for variant "${variant}".`);
}
return { names, descriptionMap };
}
function printResults(matches, query) {
if (!matches.length) {
console.log(`No matches for "${query}".`);
process.exitCode = 1;
return;
}
const n = matches.length;
console.log(`Found ${n} ${n === 1 ? 'illustration' : 'illustrations'}:`);
for (const { variant, name, tags } of matches) {
console.log(formatWithTags(`${variant}:${name}`, tags));
}
}
async function main() {
const { query, projectRoot: argProjectRoot, variant } = parseArgs(process.argv.slice(2));
if (!query) {
console.error('Error: missing query.\n' + usage);
process.exitCode = 1;
return;
}
const queryLower = query.toLowerCase();
const resolvedVariant = resolveVariant(variant);
if (variant && !resolvedVariant) {
console.error(`Error: unknown variant "${variant}". ${variants.join(', ')}`);
process.exitCode = 1;
return;
}
const projectRoot = argProjectRoot ? path.resolve(argProjectRoot) : await findProjectRoot();
if (!projectRoot) {
console.error('Error: no project root. Use --project-root.');
process.exitCode = 1;
return;
}
const pkg = await findScopedCdsPackage(projectRoot, 'cds-illustrations');
if (!pkg) {
console.error('Error: @coinbase/cds-illustrations not found in node_modules.');
process.exitCode = 1;
return;
}
const selectedVariants = resolvedVariant ? [resolvedVariant] : variants;
const matches = [];
for (const v of selectedVariants) {
try {
const { names, descriptionMap } = await loadVariantData(pkg, projectRoot, v);
for (const name of names) {
const nameHit = name.toLowerCase().includes(queryLower);
const { tags, descKeyHit } = iconTagsAndDescriptionHit(descriptionMap, name, queryLower);
if (!nameHit && !descKeyHit) continue;
matches.push({ variant: v, name, nameHit, tags });
}
} catch (error) {
console.error(
`Warning: variant "${v}" (${pkg}):`,
error instanceof Error ? error.message : error,
);
}
}
const deduped = [...new Map(matches.map((e) => [`${e.variant}:${e.name}`, e])).values()];
deduped.sort((a, b) => {
if (a.nameHit !== b.nameHit) return a.nameHit ? -1 : 1;
const byName = a.name.localeCompare(b.name);
if (byName !== 0) return byName;
return a.variant.localeCompare(b.variant);
});
printResults(deduped, query);
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});
#!/usr/bin/env bash
#
# Discovers installed CDS packages, their versions, valid export paths,
# and the CDS runtime (web or mobile).
#
# Usage:
# bash discover-cds-packages.sh [node_modules_path]
#
# If node_modules_path is omitted, walks up from $PWD to find the nearest
# node_modules directory.
#
# Output: a CDS Runtime line, then one section per discovered CDS package
# with name, version, and every valid subpath export.
set -euo pipefail
CDS_PACKAGE_SUFFIXES=(
"cds-web"
"cds-mobile"
"cds-common"
"cds-icons"
"cds-web-visualization"
"cds-mobile-visualization"
)
find_node_modules() {
local dir="${1:-$PWD}"
while [[ "$dir" != "/" ]]; do
if [[ -d "$dir/node_modules" ]]; then
echo "$dir/node_modules"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
resolve_package() {
local node_modules="$1" suffix="$2"
for scope in $(ls "$node_modules" | grep '^@' 2>/dev/null); do
if [[ -f "$node_modules/$scope/$suffix/package.json" ]]; then
echo "$scope/$suffix"
return 0
fi
done
return 1
}
if [[ $# -ge 1 ]]; then
NODE_MODULES="$1"
else
NODE_MODULES="$(find_node_modules)" || {
echo "Error: no node_modules directory found." >&2
exit 1
}
fi
# Detect CDS runtime
has_web=0
has_mobile=0
resolve_package "$NODE_MODULES" "cds-web" >/dev/null 2>&1 && has_web=1
resolve_package "$NODE_MODULES" "cds-mobile" >/dev/null 2>&1 && has_mobile=1
if [[ $has_web -eq 1 && $has_mobile -eq 1 ]]; then
echo "CDS Runtime: web (both web and mobile are installed, defaulting to web)"
elif [[ $has_web -eq 1 ]]; then
echo "CDS Runtime: web"
elif [[ $has_mobile -eq 1 ]]; then
echo "CDS Runtime: mobile"
else
echo "CDS Runtime: unknown (neither cds-web nor cds-mobile found)"
fi
echo ""
# Print package details
found=0
for suffix in "${CDS_PACKAGE_SUFFIXES[@]}"; do
pkg_name="$(resolve_package "$NODE_MODULES" "$suffix" 2>/dev/null)" || continue
found=1
pkg_json="$NODE_MODULES/$pkg_name/package.json"
version=$(node -e "console.log(require('$pkg_json').version)")
echo "=== $pkg_name@$version ==="
echo ""
node -e "
const exports = require('$pkg_json').exports || {};
const paths = Object.keys(exports)
.filter(p => p !== './package.json' && !p.toLowerCase().includes('v7'))
.map(p => p === '.' ? '$pkg_name' : '$pkg_name/' + p.slice(2))
.sort();
paths.forEach(p => console.log(' ' + p));
"
echo ""
done
if [[ $found -eq 0 ]]; then
echo "No CDS packages found in $NODE_MODULES" >&2
exit 1
fi