LeafyGreen UI Kit Compound Component
npm install @leafygreen-ui/compound-componentUtility functions for creating compound components in React. This package provides factory functions to create components with attached sub-components, following the compound component pattern.
``shell`
pnpm add @leafygreen-ui/compound-component
`shell`
yarn add @leafygreen-ui/compound-component
`shell`
npm install @leafygreen-ui/compound-component
Use CompoundComponent to create a parent component with attached sub-components:
`tsx
import {
CompoundComponent,
CompoundSubComponent,
} from '@leafygreen-ui/compound-component';
// Create a sub-component
const MySubComponent = CompoundSubComponent(
({ children }) =>
// Create the main compound component
const MyComponent = CompoundComponent(
({ children }) =>
// Usage
function App() {
return (
Main content
);
}
`
You can attach multiple sub-components to a compound component:
`tsx
const Header = CompoundSubComponent(
({ children }) =>
{
displayName: 'Header',
key: 'isHeader',
},
);
const Body = CompoundSubComponent(({ children }) =>
displayName: 'Body',
key: 'isBody',
});
const Footer = CompoundSubComponent(
({ children }) => ,
{
displayName: 'Footer',
key: 'isFooter',
},
);
const Card = CompoundComponent(
({ children }) =>
// Usage
function App() {
return (
);
}
`
Use findChild and findChildren utilities to locate specific sub-components within a parent component's children:
`tsx
import {
CompoundComponent,
CompoundSubComponent,
findChild,
findChildren,
} from '@leafygreen-ui/compound-component';
// Define property constants for type safety and consistency
const CardProperties = {
Header: 'isHeader',
Body: 'isBody',
Footer: 'isFooter',
} as const;
// Create sub-components with identifying properties
const Header = CompoundSubComponent(
({ children }) =>
{
displayName: 'Header',
key: CardProperties.Header,
},
);
const Body = CompoundSubComponent(({ children }) =>
displayName: 'Body',
key: CardProperties.Body,
});
const Footer = CompoundSubComponent(
({ children }) => ,
{
displayName: 'Footer',
key: CardProperties.Footer,
},
);
// Parent component that uses findChild/findChildren
const Card = CompoundComponent(
({ children }) => {
// Find specific sub-components using property constants
const header = findChild(children, CardProperties.Header);
const body = findChild(children, CardProperties.Body);
const footer = findChild(children, CardProperties.Footer);
// Find all instances of a sub-component type
const allBodies = findChildren(children, CardProperties.Body);
return (
// Usage - parent can control layout and add wrapper elements
function App() {
return (
);
}
`
`tsx
// Define property constants for the Modal component
const ModalProperties = {
Header: 'isModalHeader',
Body: 'isModalBody',
Footer: 'isModalFooter',
} as const;
const ModalHeader = CompoundSubComponent(
({ children }) =>
const ModalBody = CompoundSubComponent(
({ children }) =>
const ModalFooter = CompoundSubComponent(
({ children }) =>
const Modal = CompoundComponent(
({ children }) => {
// Use property constants instead of string literals
const header = findChild(children, ModalProperties.Header);
const body = findChild(children, ModalProperties.Body);
const footer = findChild(children, ModalProperties.Footer);
return (
{/ Body is required /}
{/ Footer is optional /}
{footer &&
$3
CompoundSubComponent can accept additional static properties to create hierarchical compound components without needing to nest CompoundComponent calls. This provides a cleaner DX:`tsx
import {
CompoundComponent,
CompoundSubComponent,
findChild,
} from '@leafygreen-ui/compound-component';// Define property constants for each level
const ModalProperties = {
Header: 'isModalHeader',
Body: 'isModalBody',
Footer: 'isModalFooter',
} as const;
const FooterProperties = {
PrimaryAction: 'isPrimaryAction',
SecondaryAction: 'isSecondaryAction',
} as const;
// Create the deepest level sub-components
const PrimaryAction = CompoundSubComponent(
({ children, ...props }) => (
),
{
displayName: 'PrimaryAction',
key: FooterProperties.PrimaryAction,
},
);
const SecondaryAction = CompoundSubComponent(
({ children, ...props }) => (
),
{
displayName: 'SecondaryAction',
key: FooterProperties.SecondaryAction,
},
);
// ModalFooter is BOTH a SubComponent AND has its own sub-components
// No need to wrap with CompoundComponent!
const ModalFooter = CompoundSubComponent(
({ children }) => {
// ModalFooter can use findChild for its own children
const primaryAction = findChild(children, FooterProperties.PrimaryAction);
const secondaryAction = findChild(
children,
FooterProperties.SecondaryAction,
);
return (
{secondaryAction}
{primaryAction}
);
},
{
displayName: 'ModalFooter',
key: ModalProperties.Footer,
// Attach sub-components directly!
PrimaryAction,
SecondaryAction,
},
);const ModalHeader = CompoundSubComponent(
({ children }) =>
{children},
{
displayName: 'ModalHeader',
key: ModalProperties.Header,
},
);const ModalBody = CompoundSubComponent(
({ children }) =>
{children},
{
displayName: 'ModalBody',
key: ModalProperties.Body,
},
);// Attach to parent Modal component
const Modal = CompoundComponent(
({ children }) => {
const header = findChild(children, ModalProperties.Header);
const body = findChild(children, ModalProperties.Body);
const footer = findChild(children, ModalProperties.Footer);
return (
{header}
{body}
{footer}
);
},
{
displayName: 'Modal',
Header: ModalHeader,
Body: ModalBody,
Footer: ModalFooter,
},
);// Usage: Modal.Footer.PrimaryAction works without nesting CompoundComponent!
function App() {
return (
Confirm Action
Are you sure you want to proceed?
{}}>
Cancel
{}}>
Confirm
);
}
`API
$3
Creates a compound component with attached sub-components.
Parameters:
-
componentRenderFn: The React component render function
- properties: Object containing displayName and any sub-components to attachReturns: A React component with the sub-components attached as static properties
$3
Creates a sub-component with a static key property for identification. Can optionally accept additional static properties (like nested sub-components) to create hierarchical compound components.
Parameters:
-
componentRenderFn: The React component render function
- properties: Object containing:
- displayName (required): The component display name
- key (required): The static property name to identify this component
- Additional properties (optional): Any nested sub-components or other static propertiesReturns: A React component with the specified key property set to
true and any additional properties attached$3
Finds the first child component with a matching static property.
Parameters:
-
children: Any React children (ReactNode)
- staticProperty: The static property name to check for (string)Returns: The first matching ReactElement or
undefined if not foundSearch Depth: Only searches direct children and children inside a single React Fragment level.
$3
Finds all child components with a matching static property or an array of static properties.
Parameters:
-
children: Any React children (ReactNode)
- staticProperty: The static property name(s) to check for (string | string[])Returns: Array of matching ReactElements (empty array if none found)
Search Depth: Only searches direct children and children inside a single React Fragment level.
$3
Sub-components created with
CompoundSubComponent have a static property (specified by the key parameter) set to true. This can be used for component identification:`tsx
const MySubComponent = CompoundSubComponent(() => , {
displayName: 'MySubComponent',
key: 'isMySubComponent',
});console.log(MySubComponent.isMySubComponent); // true
`Note: The
key property itself is not exposed on the component to avoid conflicts with React's built-in key` prop.