| name | dev.cds-web |
| description | USE THIS when asked to work on a new or existing (WEB) CDS React component in packages/web |
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.ts using its *BaseProps:
import type { MyComponentBaseProps } from '../category/MyComponent';
export type ComponentConfig = {
MyComponent?: ConfigResolver<MyComponentBaseProps>;
};
- Adopt
useComponentConfig in the component and destructure from merged props:
import { 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
_props as the input variable and mergedProps as 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.
import { css, cx } from '@linaria/core';
const containerCss = css`
padding: var(--space-2);
gap: var(--space-1);
background: var(--color-bgPrimary);
color: var(--color-fgPrimary);
border: 1px solid var(--color-line);
border-radius: var(--borderRadius-400);
font-size: var(--fontSize-body);
&:hover {
background: var(--color-bgPrimaryHover);
}
`;
<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:
<Box padding={{ base: 2, phone: 1, desktop: 3 }} />
Granular classNames
- Components can expose a
classNames object prop for granular overrides on child elements within the component.
- Since the keys of the
classNames object are specific to the component they should never be on the *BaseProps type
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
style and styles object props for overriding inline styles.
- As styling is a concern of that specific component, the
style and styles props should never be on the *BaseProps type.
- Styles should be merged into a single object with a
useMemo hook and applied in the correct order (default styles => style prop => styles[ELEMENT_NAME] prop).
Example:
type ComponentProps = ComponentBaseProps & {
style?: React.CSSProperties;
styles?: { root?: React.CSSProperties; label?: React.CSSProperties };
};
Animation
Use Framer Motion for complex animations:
import { 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:
<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 element
style?: React.CSSProperties - inline styles always applied to root element
- Polymorphic
as prop 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