CDS Web Package Guidelines
Component Config Adoption (Web)
Use this guidance when adding ComponentConfigProvider defaults for the specific component you are editing.
Required implementation pattern
- Register the component in
packages/web/src/core/componentConfig.tsusing its*BaseProps:
tsimport type { MyComponentBaseProps } from '../category/MyComponent'; export type ComponentConfig = { MyComponent?: ConfigResolver<MyComponentBaseProps>; };
- Adopt
useComponentConfigin the component and destructure from merged props:
tsximport { useComponentConfig } from '../hooks/useComponentConfig'; export const MyComponent = memo((_props: MyComponentProps) => { const mergedProps = useComponentConfig('MyComponent', _props); const { className, style, ...props } = mergedProps; return <Box className={className} style={style} {...props} />; });
Rules to preserve behavior
- Provider config supplies defaults only; local props must continue to win.
- Use
_propsas the input variable andmergedPropsas the configured output. - Type resolver entries with
*BaseProps(not polymorphic/full*Props). - Keep scope to prop-level theming defaults; do not alter component behavior or control flow.
- When practical during the same change, prefer arrow-function component declarations.
CSS with Linaria
Use Linaria for zero-runtime CSS. Always use CDS theme CSS variables for colors, spacing, typography, and other design tokens. Reference packages/web/src/core/theme.ts:53-119 for the CSS variable naming pattern.
tsximport { css, cx } from '@linaria/core'; const containerCss = css` /* Spacing tokens */ padding: var(--space-2); gap: var(--space-1); /* Color tokens */ background: var(--color-bgPrimary); color: var(--color-fgPrimary); border: 1px solid var(--color-line); /* Border radius tokens */ border-radius: var(--borderRadius-400); /* Typography tokens */ font-size: var(--fontSize-body); &:hover { background: var(--color-bgPrimaryHover); } `; // Merge classNames with cx utility **in CORRECT ORDER** <div className={cx( containerCss, // Base styles first isCompact && conditionClassToApply, // Conditional computed styles className, // User-provided className prop classNames.root, // granular overrides last )} />;
IMPORTANT: Using CSS variables ensures components respond correctly to theme changes (light/dark mode, brand themes).
CSS Variable Naming
Design tokens from packages/common/src/core/theme.ts map to CSS variables:
- Colors:
--color-{tokenName}(e.g.,--color-bgPrimary,--color-fgMuted) - Spacing:
--space-{scale}(e.g.,--space-2= 16px,--space-3= 24px) - Typography:
--fontSize-{font},--fontWeight-{font},--lineHeight-{font},--fontFamily-{font} - Border:
--borderRadius-{size},--borderWidth-{size} - Sizing:
--iconSize-{size},--avatarSize-{size},--controlSize-{name} - Shadows:
--shadow-{level}
Responsive Breakpoints
Reference packages/web/src/styles/media.ts for breakpoint values:
- phone: 0-767px
- tablet: 768-1279px
- desktop: 1280px+
Use the Box component's responsive prop API for responsive values:
tsx<Box padding={{ base: 2, phone: 1, desktop: 3 }} />
Granular classNames
- Components can expose a
classNamesobject prop for granular overrides on child elements within the component. - Since the keys of the
classNamesobject are specific to the component they should never be on the*BasePropstype
data-attributes
- Use data attributes for state-based styling:
data-active,data-disabled,data-variant,data-filled
Inline styles
- Web components should all expose a
styleandstylesobject props for overriding inline styles. - As styling is a concern of that specific component, the
styleandstylesprops should never be on the*BasePropstype. - Styles should be merged into a single object with a
useMemohook and applied in the correct order (default styles =>styleprop =>styles[ELEMENT_NAME]prop).
Example:
tsxtype ComponentProps = ComponentBaseProps & { style?: React.CSSProperties; styles?: { root?: React.CSSProperties; label?: React.CSSProperties }; };
Animation
Use Framer Motion for complex animations:
tsximport { m as motion, AnimatePresence } from 'framer-motion'; <AnimatePresence> {visible && ( <motion.div initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} /> )} </AnimatePresence>;
For simple transitions, prefer CSS transitions in Linaria.
Accessibility
Use ARIA attributes:
tsx<div role="group" aria-roledescription="carousel" aria-live="polite"> <button aria-pressed={isActive} tabIndex={isVisible ? 0 : -1} /> </div>
- Implement keyboard navigation (Arrow keys, Home, End) for interactive components.
- Provide descriptive labels for all interactive elements
- Associate form inputs with helper text using aria-describedby
- Use semantic HTML elements whenever possible
- Follow WCAG 2.1 AA standards for color contrast
- Support screen readers by providing descriptive labels and instructions
Web-Specific Props
className?: string- CSS class always applied to root elementstyle?: React.CSSProperties- inline styles always applied to root element- Polymorphic
asprop for element type (where applicable e.g. see Box)
Reference Components
- Carousel: compound components, imperative handle, context pattern
- Select (alpha/): generics, controlled/uncontrolled
- RollingNumber: animation config, measurement patterns


