Overview

Composing components with your own elements.

Catalyst components are designed with composition in mind. Most components accept an asChild prop which allows you to compose Catalyst's functionality onto alternative element types or your own React components.

Composing with HTML elements

By default, Catalyst components will render a default DOM element. For example, Button renders a <button> element.

import { Button } from '@pmi/catalyst-button';

export default function DefaultButton() {
  return <Button>Button</Button>;
}

Renders:

<button class="...">Button</button>

If you want to keep all the functionality of a Catalyst component like Button, but render a different element like a link, you can use the asChild prop to do so.

When asChild is set to true, Catalyst will not render a default DOM element, instead cloning the part's child and passing it the props and behavior required to make it functional.

import { Button } from '@pmi/catalyst-button';

export default function ButtonAsLink() {
  return (
    <Button asChild>
      <a href="/contact">Contact</a>
    </Button>
  );
}

Renders:

<a href="/contact" class="...">Contact</a>

This gives you full control over the underlying DOM element. You can use this to render Button as a Next.js Link or React Router Link.

Composing with your own React components

You can also use the asChild prop to compose Catalyst components with your own React components.

Your component must spread props

When composing with your own React components, your component must spread {...props} onto its underlying DOM element.

// before
const MyButton = () => <button />;

// after
const MyButton = (props) => <button {...props} />; 

Your component must forward refs

When composing with your own React components, your component must forward refs using React.forwardRef.

// before
const MyButton = (props) => <button {...props} />;

// after
const MyButton = React.forwardRef((props, forwardedRef) => <button {...props} ref={forwardedRef} />); 

Composing multiple primitives

The asChild prop composes one level deep. It works for one Catalyst component, or for one custom component. However, if you want to compose multiple Catalyst components together, you must use nested asChild props.

import { Button } from '@pmi/catalyst-button';
import { Link } from '@pmi/catalyst-link';

export default function MultipleComposition() {
  return (
    <Button asChild>
      <Link asChild>
        <a href="/contact">Contact</a>
      </Link>
    </Button>
  );
}

On this page