TypeScript
How component prop types are exported for reuse, and how some components accept a component prop that keeps type-checking after swapping the rendered element.
@chassis-ui/react is written in TypeScript, and every component's prop type is exported
alongside the component itself — there's no separate @types package and nothing to install.
Prop types
Import a component's prop type directly from @chassis-ui/react to type a wrapper around it, or a
forwardRef-based custom component that forwards the same props:
import { Button, ButtonProps } from '@chassis-ui/react'
function SubmitButton(props: ButtonProps) {
return <Button type="submit" {...props} />
}Responsive values
A layout prop that can change with the width, such as direction of Flex or span of
GridItem, is typed Responsive<T>: the value itself, or an object with a value per breakpoint,
where base is the value from the narrowest width. Import the type to pass such a prop through a
component of your own:
import { GridItem, Responsive } from '@chassis-ui/react'
function Field({ span = { base: 'full', md: 6 } }: { span?: Responsive<number | 'full'> }) {
return <GridItem span={span} />
}The second type argument is the keys the object reads, the viewport breakpoints by default. Grid
and GridItem also read the container keys, so their props are
Responsive<T, Breakpoint | ContainerBreakpoint>.
The component prop
A handful of components — Placeholder, CardImage, Skeleton, SkeletonLoader, and
MenuToggle — accept a component prop that swaps the element rendered at their root while
keeping the rest of that element's own props type-checked at the call site. Passing a framework's
own Image component, for example, exposes its fill/priority/loader props with full
autocomplete, on top of the props Placeholder already defines:
import Image from 'next/image'
import { Placeholder } from '@chassis-ui/react'
<Placeholder component={Image} src="/hero.jpg" width={800} height={400} alt="" fill priority />The asChild prop
Every component that accepts component also accepts asChild, which is the other way to change
what gets rendered. Instead of naming a tag or component for the component to render, you render
the element yourself as the only child, and the component merges its classes, props and ref onto
it:
import Link from 'next/link'
import { Button, NavLink } from '@chassis-ui/react'
<Button asChild>
<Link href="/login">Log in</Link>
</Button>
<NavLink asChild active>
<Link href="/">Home</Link>
</NavLink>The child's own props win over the component's, except that class names are combined and event
handlers are chained (the component's runs first). Use asChild when passing from a React Server
Component, where component={Link} fails to build, and whenever you'd rather write the target
element as ordinary JSX. SkeletonLoader is the one exception: it renders no element of its own,
so it has no asChild.
What the child element gets
A component treats its child as it treats the same element passed to component.
<Button asChild disabled><a href="/login"> renders what <Button component="a" href="/login" disabled> renders. What that is depends on the child:
| Child | Treated as | When the component is disabled |
|---|---|---|
<a>, or a component element with href or to | a link | aria-disabled="true", tabindex="-1", and the click is blocked |
<button>, <input> | the native control | the disabled attribute |
Any other tag, such as <div> | a plain element | as component="div": Button and CloseButton add role="button" and keyboard activation |
A component element with neither href nor to | a component | disabled is passed to it as a prop |
A router link such as <Link href="/login"> of Next.js or <Link to="/login"> of React Router
counts as a link because of its href or to: it renders an <a> and forwards what it's given,
so it needs the link's handling rather than a disabled attribute. When a link is disabled, the
child's own onClick doesn't run either. The same holds for a router link passed as component:
<Button component={Link} href="/login" disabled> is treated as a link too.
By family:
-
Button,Chip,PaginationItem,Avatarapply the table above. -
Link,NavLink,CardLink,MenuItemdo the same;MenuItemkeepsrole="menuitem"on the child. -
CloseButtongives a link, a<button>or a plain tag itsclose-buttonclasses and theCloselabel. A component child with nohreforto, such as<Button>, keeps its own look, as it does withcomponent={Button}. -
ListItem,StepperItem: a link or<button>child makes the item an action (list-actionon aListItem), andListorStepperrenders a<div>instead of a<ul>/<ol>around it, as forcomponent="a". -
Placeholderrenders the child as the image, withfluid,rounded,thumbnailandalignapplied. The child carries its ownsrc:import Image from 'next/image' <Placeholder asChild fluid rounded> <Image src="/hero.jpg" width={800} height={400} alt="" /> </Placeholder> -
Every other component has no element-specific behavior: its classes, props and ref go onto the child.
Supported components
Every component listed above uses the same generic pattern, typed against
ElementType — passing a string renders that
HTML tag with its native attributes typed, passing a component renders it with its own declared
props typed. Link's component prop is narrower by comparison: it swaps the rendered
tag or component (e.g. a router's own Link) without also changing which props are accepted,
since Link's own prop set already covers the common case.
Typing a custom target
Passing a custom component to component still enforces that component's own required props —
omitting one is a type error at the call site, the same as calling that component directly:
function BrandedImage(props: { src: string; alt: string; caption: string }) {
return (
<figure>
<img src={props.src} alt={props.alt} />
<figcaption>{props.caption}</figcaption>
</figure>
)
}
// Error: Property 'caption' is missing
<Placeholder component={BrandedImage} src="/hero.jpg" alt="" />