shadcn.io is not affiliated with official shadcn/ui
Shadcn Type Table for React and Tailwind
Expandable Prop / Type rows for component APIs.
PropType
Allow more than one row to be expanded.
Default
trueInstallation
bunx --bun shadcn@latest add https://kit.dev/r/type-table.jsonpnpm dlx shadcn@latest add https://kit.dev/r/type-table.jsonnpx shadcn@latest add https://kit.dev/r/type-table.jsonyarn shadcn@latest add https://kit.dev/r/type-table.jsonThis component depends on Accordion. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import type React from "react";
import { cn } from "@/lib/utils";
import {
Accordion,
AccordionContent,
AccordionContext,
AccordionItem,
AccordionItemContext,
AccordionTrigger,
useAccordionContext,
useAccordionItemContext,
} from "@/components/ui/accordion";
export const TypeTableContext = AccordionContext;
export const TypeTableItemContext = AccordionItemContext;
export const useTypeTableContext = useAccordionContext;
export const useTypeTableItemContext = useAccordionItemContext;
export interface TypeTableParameter {
description?: React.ReactNode;
name: string;
}
export interface TypeTableProps
extends Omit<
React.ComponentProps<typeof Accordion>,
"multiple" | "orientation"
> {
/**
* Allow more than one row to be expanded.
*
* @default true
*/
multiple?: boolean;
/**
* Label for the first column.
*
* @default "Prop"
*/
nameColumn?: string;
/**
* Row density. Compact reduces trigger padding.
*
* @default "default"
*/
size?: "default" | "compact";
}
export interface TypeTableItemProps
extends Omit<
React.ComponentProps<typeof AccordionItem>,
"value" | "children"
> {
/** Description shown when the row is open. */
children?: React.ReactNode;
/** Shown as Default in the open panel. */
defaultValue?: string;
/**
* Strike the name.
*
* @default false
*/
deprecated?: boolean;
/** Name in the first column. Also the item value unless `value` is set. */
name: string;
/**
* Renders `?` after the name.
*
* @default false
*/
optional?: boolean;
/** Parameter list when the type is a function. */
parameters?: TypeTableParameter[];
/** Return type shown in the open panel. */
returns?: React.ReactNode;
/** Type in the second column. */
type: string;
/** Full type signature in the open panel. Overrides the truncated column type. */
typeDescription?: React.ReactNode;
/** Optional href for the type in the second column. */
typeDescriptionLink?: string;
/** Unique accordion value. Defaults to `name`. */
value?: string;
}
const TYPE_DETAIL_RE = /[|&<>]/;
const typeTableRowClassName = "flex min-w-0 flex-1 items-center";
const typeNeedsDetail = (type: string) =>
type.length > 32 || TYPE_DETAIL_RE.test(type) || type.includes("=>");
const isPresent = (value: React.ReactNode) => {
if (value === undefined || value === null) {
return false;
}
if (typeof value === "string") {
return value.trim() !== "";
}
return true;
};
const resolveTypeDetail = (
typeDescription: React.ReactNode | undefined,
type: string
) => {
if (isPresent(typeDescription)) {
return typeDescription;
}
if (typeNeedsDetail(type)) {
return type;
}
};
const stopRowToggle = (event: React.MouseEvent) => {
event.stopPropagation();
};
const CodeValue = (props: { children: string; block?: boolean }) => {
const { children, block = false } = props;
return (
<code
className={cn(
"min-w-0 font-mono text-foreground",
"rounded-md border border-border bg-muted",
block
? "block w-full whitespace-normal break-words px-2 py-1.5"
: "inline-block max-w-full whitespace-normal break-words px-1.5 py-0.5"
)}
>
{children}
</code>
);
};
const TypeMeta = (props: { label: string; children: React.ReactNode }) => {
const { label, children } = props;
return (
<>
<p className="text-muted-foreground">{label}</p>
<div className="min-w-0">
{typeof children === "string" ? (
<CodeValue block={children.length > 40}>{children}</CodeValue>
) : (
children
)}
</div>
</>
);
};
const TypeTableHeader = (props: { nameColumn: string }) => (
<div
className={cn(
"flex w-full min-w-0 items-center justify-between gap-3",
"px-3 py-1",
"font-medium text-muted-foreground text-xs",
"group-data-[size=compact]/type-table:py-0.5"
)}
data-slot="type-table-header"
>
<span className={typeTableRowClassName}>
<span className="w-1/4 min-w-fit pe-2">{props.nameColumn}</span>
<span className="@max-[20rem]:hidden min-w-0">Type</span>
</span>
<span aria-hidden="true" className="size-4 shrink-0" />
</div>
);
const typeColumnClassName =
"@max-[20rem]:hidden min-w-0 truncate font-mono text-muted-foreground";
const TypeColumn = (props: { href?: string; type: string }) => {
const { href, type } = props;
if (href) {
return (
<a
className={cn(
typeColumnClassName,
"underline underline-offset-4 hover:text-foreground"
)}
data-slot="type-table-type"
href={href}
onClick={stopRowToggle}
title={type}
>
{type}
</a>
);
}
return (
<span
className={typeColumnClassName}
data-slot="type-table-type"
title={type}
>
{type}
</span>
);
};
const ParameterList = (props: { parameters: TypeTableParameter[] }) => {
const { parameters } = props;
return (
<div className="flex flex-col gap-2">
{parameters.map((parameter) => (
<div className="flex flex-wrap items-center gap-1" key={parameter.name}>
<span className="text-nowrap font-medium font-mono">
{parameter.name}
</span>
{isPresent(parameter.description) ? (
<>
<span aria-hidden="true">–</span>
<span>{parameter.description}</span>
</>
) : null}
</div>
))}
</div>
);
};
const TypeTableItemPanel = (props: {
body?: React.ReactNode;
defaultValue?: string;
parameters?: TypeTableParameter[];
returns?: React.ReactNode;
typeDetail?: React.ReactNode;
}) => {
const { body, defaultValue, parameters, returns, typeDetail } = props;
const hasParameters = (parameters?.length ?? 0) > 0;
const hasMeta =
typeDetail !== undefined ||
defaultValue !== undefined ||
hasParameters ||
isPresent(returns);
if (body === undefined && !hasMeta) {
return null;
}
return (
<AccordionContent className="rounded-none border-t [&>div]:p-3">
<div className="grid grid-cols-[1fr_3fr] items-start gap-x-3 gap-y-4 text-sm">
{body === undefined ? null : (
<div className="col-span-full text-pretty">{body}</div>
)}
{typeDetail === undefined ? null : (
<TypeMeta label="Type">{typeDetail}</TypeMeta>
)}
{defaultValue === undefined ? null : (
<TypeMeta label="Default">{defaultValue}</TypeMeta>
)}
{hasParameters ? (
<TypeMeta label="Parameters">
<ParameterList parameters={parameters ?? []} />
</TypeMeta>
) : null}
{isPresent(returns) ? (
<TypeMeta label="Returns">{returns}</TypeMeta>
) : null}
</div>
</AccordionContent>
);
};
export const TypeTable = (props: TypeTableProps) => {
const {
multiple = true,
size = "default",
collapsible = true,
nameColumn = "Prop",
className,
children,
...rest
} = props;
return (
<Accordion
className={cn(
"group/type-table not-prose",
"@container flex w-full flex-col overflow-hidden rounded-xl border bg-card p-1 text-card-foreground text-sm",
className
)}
collapsible={collapsible}
data-size={size}
data-slot="type-table"
multiple={multiple}
{...rest}
>
<TypeTableHeader nameColumn={nameColumn} />
{children}
</Accordion>
);
};
export const TypeTableItem = (props: TypeTableItemProps) => {
const {
name,
type,
optional = false,
defaultValue,
deprecated = false,
value,
children,
className,
parameters,
returns,
typeDescription,
typeDescriptionLink,
...rest
} = props;
const hasPanel =
isPresent(children) ||
defaultValue !== undefined ||
(parameters?.length ?? 0) > 0 ||
isPresent(returns) ||
resolveTypeDetail(typeDescription, type) !== undefined;
const row = (
<span className={typeTableRowClassName}>
<code
className={cn(
"w-1/4 min-w-fit break-words pe-2 text-start font-medium font-mono text-primary",
deprecated && "text-primary/50 line-through"
)}
data-slot="type-table-name"
>
{name}
{optional ? "?" : null}
</code>
<TypeColumn href={typeDescriptionLink} type={type} />
</span>
);
if (!hasPanel) {
return (
<div
className={cn(
"flex scroll-mt-20 items-center justify-between gap-3 overflow-hidden rounded-lg px-3 py-2",
"min-h-9",
"group-data-[size=compact]/type-table:min-h-7 group-data-[size=compact]/type-table:py-1",
className
)}
data-slot="type-table-item"
>
{row}
<span aria-hidden="true" className="size-4 shrink-0" />
</div>
);
}
return (
<AccordionItem
className={cn(
"scroll-mt-20 overflow-hidden rounded-lg border border-transparent last:border-b",
"transition-all",
"data-[state=open]:border-border data-[state=open]:bg-background data-[state=open]:shadow-sm",
"data-[state=open]:not-last:mb-2",
className
)}
data-slot="type-table-item"
value={value ?? name}
{...rest}
>
<AccordionTrigger
className={cn(
"rounded-lg px-3 font-normal",
"hover:bg-accent",
"data-[orientation=vertical]:min-h-9 data-[orientation=vertical]:py-2",
"group-data-[size=compact]/type-table:data-[orientation=vertical]:min-h-7",
"group-data-[size=compact]/type-table:data-[orientation=vertical]:py-1"
)}
>
{row}
</AccordionTrigger>
<TypeTableItemPanel
body={isPresent(children) ? children : undefined}
defaultValue={defaultValue}
parameters={parameters}
returns={returns}
typeDetail={resolveTypeDetail(typeDescription, type)}
/>
</AccordionItem>
);
};Update the import paths to match your project setup.
Usage
import {
TypeTable,
TypeTableItem,
} from "@/components/ui/type-table";<TypeTable defaultValue={["multiple"]}>
<TypeTableItem name="children" type="ReactNode">
TypeTableItem rows.
</TypeTableItem>
<TypeTableItem defaultValue="true" name="multiple" optional type="boolean">
Allow more than one row to be expanded.
</TypeTableItem>
<TypeTableItem name="value" optional type="string[]">
Controlled expanded item values.
</TypeTableItem>
<TypeTableItem name="defaultValue" optional type="string[]">
Uncontrolled expanded item values.
</TypeTableItem>
<TypeTableItem
name="onValueChange"
optional
parameters={[{ description: "{ value: string[] }", name: "details" }]}
returns="void"
type="(details: { value: string[] }) => void"
>
Called when expanded items change.
</TypeTableItem>
<TypeTableItem name="disabled" optional type="boolean">
Disable every row.
</TypeTableItem>
<TypeTableItem
defaultValue='"default"'
name="size"
optional
type='"default" | "compact"'
>
Row density.
</TypeTableItem>
<TypeTableItem name="className" optional type="string">
Class names on the root.
</TypeTableItem>
</TypeTable>Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
ArrowDown | Move to the next item when orientation is "vertical". |
ArrowUp | Move to the previous item when orientation is "vertical". |
ArrowRight | Move to the next item. |
ArrowLeft | Move to the previous item. |
Home | Move to the first item. |
End | Move to the last item. |
Demos
Controlled
Deprecated
Disabled
Optional
Single
Size Compact
API Reference
TypeTable
PropType
AttributeType
TypeTableItem
PropType
AttributeType