Skip to main content

slotProps

Pass props directly to individual internal slots of Abyss components.

github
View source code

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.
Note

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:

PropSlot typeBehavior
classNameLeaf onlyMerged with the slot's existing classes (does not replace them)
cssLeaf onlyMerged with any component-level css prop; slot-level values win on conflict
styleLeaf onlyApplied as inline styles on the slot element
data-testidLeaf onlyOverrides the auto-generated data-testid for that slot
data-*Leaf onlySpread directly onto the slot element
slotPropsSub-componentNested; 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 only slotProps.
  • Hover: hover over the slot key; no typing required. The Tooltip starts with Abyss.SlotPropsLeafValue for a leaf slot, or Abyss.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.

  • className is merged alongside the slot's own Abyss classes; it does not replace them.
  • css is the recommended replacement for the css={{ 'abyss-button-root': {} }} keyed form. Slot names come directly from the Classes table (no abyss- 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:

Info

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',
        },
      }}
    />
  );
};
Editable Example
Info

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>
  );
};
Editable Example

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.

GoalRecommended approach
Apply a design change everywhere the token is usedcreateTheme
Build a reusable styled variant used in multiple placesstyled tool
Give a specific slot a predictable test IDslotProps with data-testid
Track a slot with a data-* attributeslotProps
Make a one-off style exception for a single instanceslotProps with css or className
Apply the same override to every slot in one instanceComponent-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>
Table of Contents