Skip to contents

Composition

A guide to composing Base UI components with your own Solid components.

Composing custom Solid components

Use the render prop to compose a Base UI part with your own Solid components.

For example, most triggers render a <button> by default. The code snippet below shows how to use a custom button instead.

index.tsx
// prettier-ignore
<Menu.Trigger render={(props) => <MyButton size="md" {...props} />}>
  Open menu
</Menu.Trigger>

The custom component must forward the ref, and spread all the received props on its underlying DOM node.

Composing multiple components

In situations where you need to compose multiple Base UI components with custom Solid components, render props can be nested as deeply as necessary. Working with Tooltip is a common example.

index.tsx
<Dialog.Root>
  <Tooltip.Root>
    <Tooltip.Trigger
      render={(tooltipProps) => (
        <Dialog.Trigger
          {...tooltipProps}
          render={(dialogProps) => (
            <Menu.Trigger
              {...dialogProps}
              render={(menuProps) => <MyButton size="md" {...menuProps} />}
            >
              Open menu
            </Menu.Trigger>
          )}
        />
      )}
    />
    <Tooltip.Portal>...</Tooltip.Portal>
  </Tooltip.Root>
  <Dialog.Portal>...</Dialog.Portal>
</Dialog.Root>

Changing the default rendered element

You can also use the render prop to override the rendered element of the component.

For example, <Accordion.Header> renders an <h3> element by default. The code snippet below shows how to render it as an <h2> element so that it fits the heading hierarchy of the page.

index.tsx
import { Accordion } from 'base-ui-solid/accordion';

export default () => (
  <Accordion.Root>
    <Accordion.Item>
      <Accordion.Header render="h2">
        <Accordion.Trigger>What is Base UI?</Accordion.Trigger>
      </Accordion.Header>
      <Accordion.Panel>Base UI is a library of unstyled Solid components.</Accordion.Panel>
    </Accordion.Item>
  </Accordion.Root>
);

Each Base UI component renders the most appropriate element by default, and in most cases, rendering a different element is recommended only on a case-by-case basis.

Render function

Use a function, component, or tag name for render. Solid cannot clone JSX elements. Read the callback props and state reactively, without destructuring them.

switch.tsx
<Switch.Thumb
  render={(props, state) =>
    // prettier-ignore
    <span {...props}>
      {state.checked ? <CheckedIcon /> : <UncheckedIcon />}
    </span>
  }
/>

Using a function gives you complete control over spreading props and also allows you to render different content based on the component’s state.