Description
We should define and test the stable public hooks and state surfaces that components expose as part of their supported contract.
This issue is not about encouraging arbitrary selector-based styling against component internals. Instead, it is about identifying the public hooks, attributes, and state indicators that consumers and tests are allowed to rely on, and ensuring those behaviors are protected with automated tests.
Where Rad UI intentionally exposes stable hooks or state-related attributes, those surfaces should be treated as part of the public API and covered by tests so they do not regress during refactors.
Problem
Without clearly defined and tested public hooks/state surfaces:
- consumers may rely on undocumented internal structure
- maintainers may accidentally break supported hooks during internal refactors
- tests may miss regressions in public state signaling
- it becomes unclear which attributes/classes are stable contract versus incidental implementation detail
- component behavior becomes harder to verify consistently across the library
This is especially relevant for stateful and interactive components where public state indicators are often necessary for composition, testing, and supported customization.
Intended Position
The documentation and tests should make the following explicit:
- only documented hooks/state surfaces are considered stable
- internal DOM structure and incidental classes are not automatically public API
- public hooks may include stable classes, data attributes, ARIA/state attributes, slot markers, or other intentionally exposed selectors
- those public hooks must be validated by automated tests
- unsupported internal implementation details should not be treated as stable contract
Proposed Scope
Audit the component library and define which stable hooks/state surfaces are intentionally supported, then add test coverage for them.
This may include, where applicable:
- stable root/slot class hooks
- public
data-* state attributes
- disabled/selected/open/checked/orientation state markers
- attributes used as part of the documented component contract
- other intentionally exposed behavior/state indicators
Behavior and Test Expectations
This issue should cover behavior, not just stylability.
Examples of what should be validated:
- Public state attributes appear when the component enters a given state
- State attributes update correctly as component state changes
- Disabled/interactive markers reflect actual component behavior
- Supported hooks remain present on the expected public element
- Documented hooks are not removed or renamed unintentionally
Example Cases
For a disclosure-style component, tests may validate behavior like:
render(
<Accordion.Root>
<Accordion.Item value="item-1">
<Accordion.Trigger>Toggle</Accordion.Trigger>
<Accordion.Content>Content</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
);
Expected assertions could include:
- trigger exposes documented closed-state hook initially
- trigger/content update to documented open-state hook after interaction
- state markers reflect actual open/closed behavior
For a selectable component, tests may validate:
render(
<Tabs.Root defaultValue="account">
<Tabs.List>
<Tabs.Trigger value="account">Account</Tabs.Trigger>
<Tabs.Trigger value="password">Password</Tabs.Trigger>
</Tabs.List>
</Tabs.Root>
);
Expected assertions could include:
- active trigger exposes the documented selected-state hook
- inactive trigger exposes the documented unselected state
- state markers update correctly when selection changes
Proposed Deliverables
- Define the stable hook/state contract for relevant components.
- Add automated tests covering those documented public surfaces.
- Update docs to distinguish stable public hooks from internal implementation details.
- Ensure examples and guidance reflect the supported contract accurately.
Acceptance Criteria
- Public stable hooks/state surfaces are identified for relevant components.
- Automated tests are added to verify those hooks and state behaviors.
- Tests cover state transitions, not just static presence.
- Docs clarify which hooks are supported and which internal details are not guaranteed.
- Unsupported internal selectors/classes are not implicitly treated as public API.
Notes
This issue should protect intentional public behavior, not expand the public API unnecessarily. The goal is to make supported hooks explicit, test them rigorously, and avoid accidental regressions while preserving freedom to refactor internal implementation details.
Description
We should define and test the stable public hooks and state surfaces that components expose as part of their supported contract.
This issue is not about encouraging arbitrary selector-based styling against component internals. Instead, it is about identifying the public hooks, attributes, and state indicators that consumers and tests are allowed to rely on, and ensuring those behaviors are protected with automated tests.
Where Rad UI intentionally exposes stable hooks or state-related attributes, those surfaces should be treated as part of the public API and covered by tests so they do not regress during refactors.
Problem
Without clearly defined and tested public hooks/state surfaces:
This is especially relevant for stateful and interactive components where public state indicators are often necessary for composition, testing, and supported customization.
Intended Position
The documentation and tests should make the following explicit:
Proposed Scope
Audit the component library and define which stable hooks/state surfaces are intentionally supported, then add test coverage for them.
This may include, where applicable:
data-*state attributesBehavior and Test Expectations
This issue should cover behavior, not just stylability.
Examples of what should be validated:
Example Cases
For a disclosure-style component, tests may validate behavior like:
Expected assertions could include:
For a selectable component, tests may validate:
Expected assertions could include:
Proposed Deliverables
Acceptance Criteria
Notes
This issue should protect intentional public behavior, not expand the public API unnecessarily. The goal is to make supported hooks explicit, test them rigorously, and avoid accidental regressions while preserving freedom to refactor internal implementation details.