Composition

Learn how to compose components in chakra-ui-solid.

The as Prop

Used to change the underlying HTML element that a component renders. It provides a straightforward way to change the underlying element while retaining the component’s functionality.

<Heading as="h3">Hello, world!</Heading>

TypeScript: the caveat with the as prop is that the type of the component passed to it must be compatible with the component’s props. We do not infer the underlying component’s props from as.

as stays a loose ValidComponent rather than a generic that re-types props from the element, and that is deliberate rather than unfinished: the deep conditional types that would buy you href on as="a" also wreck editor completions across every component in the library.

This is a Box rendered as a section
import { Box } from "chakra-ui-solid";

export default function BoxWithAsProp() {
  return (
    <Box as="section" color="fg.muted">
      This is a Box rendered as a section
    </Box>
  );
}

The render Prop

Used to compose a component’s functionality onto an element you supply — the React version’s asChild, which this library does not have.

It is a function that receives the computed props and returns the element, never a JSX element. A Solid JSX element is an already-constructed node by the time it reaches us and there is no cloneElement, so accepting one could only mean rendering it with every computed prop dropped.

<Button render={(props) => <MyButton {...props}>Open</MyButton>} />
import type { JSX } from "@solidjs/web";
import { Box } from "chakra-ui-solid";

export default function BoxWithRenderProp() {
  return (
    <Box
      px="4"
      py="2"
      borderRadius="l2"
      bg="colorPalette.solid"
      color="colorPalette.contrast"
      textDecoration="none"
      render={(props) => (
        <a {...(props as JSX.AnchorHTMLAttributes<HTMLAnchorElement>)} href="https://panda-css.com">
          {props.children}
        </a>
      )}
    >
      A styled anchor
    </Box>
  );
}

That cast is real, and it is narrow. Every component types its element props against the element it renders — Box’s are a div’s, Span’s a span’s — and Solid’s Ref<HTMLDivElement> is not assignable to Ref<HTMLAnchorElement>, so a render target that is a different host element needs a cast. A target that renders the same element needs none, and neither does a component typed against the same JSX.HTMLAttributes. A component whose props are a Chakra type needs one for a different reason, which Best Practices below spells out.

The unstyled Prop

Used to drop a component’s own theme styles while keeping everything else it does. Style props and the css prop still apply — the opt-out is of the recipe, not of styling.

<Checkmark unstyled css={{ color: "fg.inverted" }} />

This is how one component re-dresses another it is built from, rather than a way to render a bare element: a checkbox built on Checkmark renders <Checkmark unstyled> and hands it its own slot’s styles, so the mark takes the checkbox’s size and colour instead of the checkmark recipe’s.

Best Practices

as and render both hand your element every prop the component computed for it — the composed class, the merged ref, the children, and the DOM attributes. To avoid common pitfalls, there are a few practices to consider:

  • Spread the props. A target that picks them off by name loses whichever it did not name — the class, the ref, an attribute — and nothing errors.
  • Never destructure them. They are a live props proxy whose class is a getter that recomputes when a style prop changes, so render={({ class: className }) => …} reads it once and freezes the element at whatever it was on the first render.
  • Refs need no forwarding. Solid has no forwardRef: ref arrives as an ordinary prop, and the spread applies it whether the target is a host element or another chakra-ui-solid component. Dropping the spread is the only way to lose it.
const MyButton = (props: ComponentProps<"button">) => <button {...props} />;
 
// with `as` — Button renders MyButton in place of its own element
<Button as={MyButton}>Click me</Button>
 
// with `render` — the same target, called with the computed props
<Button render={(props) => <MyButton {...props}>Click me</MyButton>} />

Type the target against the element’s props, as MyButton does, rather than against the Chakra component’s. What a target is handed is the DOM props, and a Chakra props type spells five of those attributes htmlSize, htmlWidth, htmlHeight, htmlTranslate and htmlContent — keeping the plain names for the style props of the same name — so a target typed BoxProps or ButtonProps does not accept them without a cast.