Component State
Learn how to manage component state using Context and Provider components.
Need to access a component's state? You have three options:
| Approach | When to use it |
|---|---|
Component.Context | Quick inline access via render props |
use*Context hooks | Build custom child components that read state |
useComponent + RootProvider | Control the component from outside |
Context Components
Use Component.Context to access state inline via render props. Here, Avatar.Fallback reads the loaded state to
show different content:
Good to know (RSC): The render prop pattern requires the
'use client'directive when using React Server Components.
import { Avatar } from '@ark-ui/react/avatar'
import styles from './index.module.css'
export const Context = () => (
<Avatar.Root className={styles.Root}>
<Avatar.Context>
{(avatar) => <Avatar.Fallback className={styles.Fallback}>{avatar.loaded ? 'PA' : 'Loading'}</Avatar.Fallback>}
</Avatar.Context>
<Avatar.Image className={styles.Image} src="https://i.pravatar.cc/300?u=a" alt="avatar" />
</Avatar.Root>
)
Context Hooks
Every component exports a use*Context hook (like useDialogContext or useMenuContext). Call it from any child
component to access state and methods—no render props needed.
import { Dialog, useDialogContext } from '@ark-ui/react/dialog'
function CustomCloseButton() {
const dialog = useDialogContext()
return <button onClick={() => dialog.setOpen(false)}>Close ({dialog.open ? 'open' : 'closed'})</button>
}
export const Demo = () => (
<Dialog.Root>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Content>
<CustomCloseButton />
</Dialog.Content>
</Dialog.Root>
)
The hook returns the same API as Component.Context, just without the nesting.
Provider Components
Need to control a component from outside its tree? Use a useComponent hook (like useDialog) with RootProvider.
When you use
RootProvider, skip theRootcomponent—you don't need both.
import { Accordion, useAccordion } from '@ark-ui/react/accordion'
import { ChevronDownIcon } from 'lucide-react'
import styles from './index.module.css'
export const RootProvider = () => {
const accordion = useAccordion({
multiple: true,
defaultValue: ['ark-ui'],
})
return (
<div className="stack">
<output>Value: {JSON.stringify(accordion.value)}</output>
<Accordion.RootProvider className={styles.Root} value={accordion}>
{items.map((item) => (
<Accordion.Item className={styles.Item} key={item.value} value={item.value}>
<Accordion.ItemTrigger className={styles.ItemTrigger}>
{item.title}
<Accordion.ItemIndicator className={styles.ItemIndicator}>
<ChevronDownIcon />
</Accordion.ItemIndicator>
</Accordion.ItemTrigger>
<Accordion.ItemContent className={styles.ItemContent}>
<div className={styles.ItemBody}>{item.content}</div>
</Accordion.ItemContent>
</Accordion.Item>
))}
</Accordion.RootProvider>
</div>
)
}
const items = [
{
value: 'ark-ui',
title: 'What is Ark UI?',
content: 'A headless component library for building accessible web apps.',
},
{
value: 'getting-started',
title: 'How to get started?',
content: 'Install the package and import the components you need.',
},
{
value: 'maintainers',
title: 'Who maintains this project?',
content: 'Ark UI is maintained by the Chakra UI team.',
},
]
Choosing the Right Approach
Component.Context— Quick inline access for conditional renderinguse*Contexthooks — Build reusable child components that need parent stateuseComponent+RootProvider— Trigger actions from outside (like opening a dialog from a menu item)