The slotProps prop is available on every Abyss component. It passes css, className, data-testid, and other props to individual internal elements, called slots. Unlike top-level css and className, it targets one slot instead of the whole component tree.
<Button slotProps={{ 'button-root': { css: { paddingInline: '32px' }, className: 'form-action-btn', 'data-testid': 'submit-claim-btn', }, }}> Submit claim</Button>Finding slot names
Slot names correspond to the class names in the Classes table on each component's Integration tab, only without the abyss- prefix. For example, a class named abyss-button-root maps to the slot key 'button-root'.
You can find slot names three ways:
- Go to any component's documentation page, click the Integration tab, and refer to the Classes table.
- Inspect the element in your browser's DevTools and look for
abyss-*class names. - Start typing inside
slotProps={{ }}in your editor. TypeScript will autocomplete every valid slot name for that component (see TypeScript and autocomplete below). This is also the fastest way to tell a leaf slot from a sub-component slot. See the note at the end of the next section for more on those.
The same slot names are used in the css prop, so the Integration tab is the single source of truth for both.
Supported props
Each value in slotProps is an object that can include:
| Prop | Slot type | Behavior |
|---|---|---|
className | Leaf only | Merged with the slot's existing classes (does not replace them) |
css | Leaf only | Merged with any component-level css prop; slot-level values win on conflict |
style | Leaf only | Applied as inline styles on the slot element |
data-testid | Leaf only | Overrides the auto-generated data-testid for that slot |
data-* | Leaf only | Spread directly onto the slot element |
slotProps | Sub-component | Nested; propagated to the Abyss sub-component rendered within this slot |
Leaf slots target a DOM element directly (e.g., 'button-root', 'button-content-container').
Sub-component slots render another Abyss component internally (e.g., 'button-loading-spinner').
Use TypeScript to tell them apart:
- Autocomplete: type the slot key, then trigger completions inside its value object. A leaf slot offers
css/className/style/data-testid; a sub-component slot offers onlyslotProps. - Hover: hover over the slot key; no typing required. The Tooltip starts with
Abyss.SlotPropsLeafValuefor a leaf slot, orAbyss.SlotPropsSubComponentValue<...>for a sub-component slot. The type name at the start is what to look for; the rest of the Tooltip expands the component's full internal shape and can look noisy.
The Classes table is a useful hint: a sub-component slot's description names the component it renders. When it is ambiguous, use the type.
Scoped className and CSS
Use slotProps when a style must apply to one specific slot. Component-level css and className apply across the component tree.
classNameis merged alongside the slot's own Abyss classes; it does not replace them.cssis the recommended replacement for thecss={{ 'abyss-button-root': {} }}keyed form. Slot names come directly from the Classes table (noabyss-prefix), and slot-level values always win over component-level styles on any conflicting property.
Testing
The top-level data-testid prop auto-generates compound IDs like my-btn-abyss-button-root for every slot, which is useful as a namespace, but often verbose in tests. Use slotProps to give a specific slot a clean, stable ID directly:
For more on component testing and how data-testid works across slots, see the Component testing guide.
Custom data attributes
Use slotProps to attach data-* attributes to a specific element. Some potential use cases are analytics tracking, feature flags, or any integration that targets DOM attributes.
Sub-component slots
When a slot renders another Abyss component, nest through that slot's slotProps key to reach its internal elements. Those slots are not available at the top level.
In this SelectInput example, 'select-input-selections' routes to Selections and 'select-input-menu-search-wrapper' routes to SearchFilter. The third slot, 'select-input-menu-option', is a related but different case; see the note below.
() => { const options = [ { label: 'Allergies & Immunology', value: 'allergy' }, { label: 'Cardiovascular Disease', value: 'cardio' }, { label: 'Endocrinology & Metabolism', value: 'endo' }, { label: 'General Practice', value: 'general' }, ]; const [value, setValue] = useState<string[]>([]); return ( <SelectInput label="Specialty" isSearchable isMulti options={options} value={value} onChange={setValue} slotProps={{ // Selections sub-component — a stable ID for the selected-count text 'select-input-selections': { slotProps: { 'select-input-selection-count': { 'data-testid': 'specialty-selection-count', }, }, }, // SearchFilter sub-component — a stable ID for the search input 'select-input-menu-search-wrapper': { slotProps: { 'select-input-menu-search-input': { 'data-testid': 'specialty-search-input', }, }, }, // SelectionOption sub-component — a stable ID for each dropdown option 'select-input-menu-option': { slotProps: { 'select-input-menu-option-label': { 'data-testid': 'specialty-menu-option-label', }, }, 'data-testid': 'specialty-menu-option', }, }} /> ); };
When using TypeScript, most sub-component slot entries only accept slotProps. css, style, className, aria-*, data-*, and data-testid are intentionally excluded, because those props would apply at the sub-component level rather than to a specific DOM element, making their effect unpredictable. Use nested slotProps to reach the sub-component's own internal slots instead.
A few slots are the exception: 'select-input-menu-option' (see example above) targets SelectionOption, but its own root element is what that slot resolves to directly, so it accepts css, className, data-testid, etc. directly, the same as a plain leaf, in addition to nested slotProps for reaching SelectionOption's own internal slots (its label, Checkbox, etc.). The Classes table doesn't distinguish this case explicitly. If a sub-component slot rejects a prop you expect it to accept, try it directly first; TypeScript will tell you if it's not supported.
Nested slotProps
Nesting follows the real component tree and can continue as deeply as needed.
Here, 'button-loading-spinner' targets Button's internal LoadingSpinner and reaches its otherwise inaccessible slots.
() => { return ( <Group> <Button isLoading ariaLoadingLabel="Submitting..."> Submit claim </Button> {/* * 'button-loading-spinner' renders LoadingSpinner internally. * Its slots are only reachable via nested slotProps. */} <Button isLoading ariaLoadingLabel="Submitting..." slotProps={{ 'button-loading-spinner': { slotProps: { 'loading-spinner-bar': { css: { animationDuration: '2.5s' }, }, 'loading-spinner-root': { 'data-testid': 'submit-spinner', }, }, }, }} > Submit claim </Button> </Group> ); };
Here's the same idea carried three components deep: SelectInput renders a SelectionOption for each dropdown option, SelectionOption renders a FormInputCheckbox for the Checkbox, and FormInputCheckbox renders the actual <input> element. Nesting slotProps three times reaches that <input> directly:
When to use which prop
Design changes that should apply everywhere belong in the token system, not in slotProps; use createTheme or styled first. slotProps is for one-off, targeted needs: a stable test ID, a tracking attribute, or a genuinely one-instance style exception.
| Goal | Recommended approach |
|---|---|
| Apply a design change everywhere the token is used | createTheme |
| Build a reusable styled variant used in multiple places | styled tool |
| Give a specific slot a predictable test ID | slotProps with data-testid |
Track a slot with a data-* attribute | slotProps |
| Make a one-off style exception for a single instance | slotProps with css or className |
| Apply the same override to every slot in one instance | Component-level css or className |
TypeScript and autocomplete
Slot keys are typed against the component's own slot names, the same generic type variable T used in the css prop. Your IDE will autocomplete valid slot names and warn on typos.
<Button slotProps={{ 'button-ro': { css: { fontWeight: 700 } }, // TypeScript error: not a valid ButtonClasses slot 'button-root': { css: { fontWeight: 700 } }, // ✓ }}> Button</Button>