Rule Group
RuleGroup is a set of building blocks for composing a group of items joined by AND, with groups joined by OR - for example a rule builder or a conditional filter form.
It ships as four independent, presentational components with no shared state and no required nesting or order, bundled together under the RuleGroup namespace for discoverability:
RuleGroup.Panel- the container for a group of AND-ed itemsRuleGroup.Separator- a horizontal line with an "OR" label and iconRuleGroup.AndButton- a button that adds another item to a groupRuleGroup.OrButton- a button that adds another group
Because there is no fixed place for the separator or the buttons, you can assemble them in whatever composition your form needs.
Example
<RuleGroup.Panel> <div>Revenue > $100</div> <RuleGroup.AndButton onClick={() => {}} /> </RuleGroup.Panel>
Compositions
Two real usages of this pattern compose the same four pieces differently. Both are shown below.
OR button inside the last group
Here the separator only appears between groups, and the OrButton sits inside the last group's panel, next to its AndButton.
function OrInsideLastGroup() { const groups = [ ['Revenue > $100', 'Country = US'], ['Installs > 1000'] ]; return ( <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> {groups.map((rows, groupIndex) => { const isLastGroup = groupIndex === groups.length - 1; return ( // Render the separator as a sibling of the panel, not nested // inside a shared wrapper, so the flex `gap` above spaces both // of them evenly - a wrapper div would only space between groups // and leave the panel touching the separator right above it. <Fragment key={rows.join('-')}> {groupIndex > 0 && <RuleGroup.Separator />} <RuleGroup.Panel> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> {rows.map((row) => ( <div key={row}>{row}</div> ))} </div> <div style={{ display: 'flex', gap: 8, marginTop: 12 }}> <RuleGroup.AndButton onClick={() => {}} /> {isLastGroup && <RuleGroup.OrButton onClick={() => {}} />} </div> </RuleGroup.Panel> </Fragment> ); })} </div> ); }
Single OR button after all groups
Here the separator still appears between groups, but a single OrButton is rendered once, after every panel, instead of inside one.
function SingleOrButtonAfterGroups() { const groups = [ ['Event = purchase', 'Value >= 10'], ['Event = signup'] ]; return ( <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> {groups.map((rows, groupIndex) => ( // Same rule as above: separator and panel are siblings so the // flex `gap` spaces both, instead of being nested in one wrapper. <Fragment key={rows.join('-')}> {groupIndex > 0 && <RuleGroup.Separator />} <RuleGroup.Panel> <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}> {rows.map((row) => ( <div key={row}>{row}</div> ))} </div> <div style={{ marginTop: 12 }}> <RuleGroup.AndButton onClick={() => {}} /> </div> </RuleGroup.Panel> </Fragment> ))} <RuleGroup.OrButton onClick={() => {}} /> </div> ); }
Usage Rules
Do:
- Assemble
Panel,Separator,AndButtonandOrButtonin whatever order and location fits your form - Override the default
AND/ORlabel when it helps clarify what a button adds (e.g. "Add condition") - Let the visible label drive the accessible name - avoid passing an
aria-labelthat doesn't include the visible text
Don't:
- Assume
Panel,Separatoror the buttons must be nested inside one another - they don't share any state - Hardcode the separator's icon/label if your product needs a different logical operator - both
iconNameandlabelare configurable
Props
RuleGroup.Panel
| Name | Type | Default |
|---|---|---|
children The content of the group panel - typically a set of AND-ed rows and an AndButton |
| — |
css Add custom styles to this component. Use with caution. Learn more here: Link |
| — |
inputFieldCss |
| — |
data-{foo} Data attributes can be used by testing libraries to retrieve components or assert their existence |
| — |
| * - the prop is required. |
RuleGroup.Separator
| Name | Type | Default |
|---|---|---|
label The label rendered in the center of the separator |
| "OR" |
iconName Name of the Icon rendered next to the label. Refer to the full list here: Link | "AddValue" | "Adjust" | "AIAgent" | "AppGrid" | "ArrowCircleLeft" | "ArrowCircleRight" | "ArrowDown"... | "ArrowSplit" |
data-{foo} Data attributes can be used by testing libraries to retrieve components or assert their existence |
| — |
| * - the prop is required. |
RuleGroup.AndButton
| Name | Type | Default |
|---|---|---|
label Overrides the default 'AND'/'OR' label |
| — |
onMouseEnter Native HTML callback: Link |
| — |
onMouseLeave Native HTML callback: Link |
| — |
aria-label Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link |
| — |
aria-labelledby Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link |
| — |
disabled Use this prop if the component should not be interactive |
| — |
onClick Native HTML callback: Link |
| — |
id Native HTML attribute: Link . |
| — |
type Native HTML attribute: Link |
| — |
isHighlighted isHighlighted boolean for Highlighted tertiary button |
| — |
size Size of the button |
| — |
onFocus Native HTML callback: Link |
| — |
onBlur Native HTML callback: Link |
| — |
onKeyDown Native HTML callback: Link |
| — |
kind Kind of the button | "negative" | "primary" | "secondary" | "tertiary" | "dark" | "floating" | "feedback-info-primary" | ... | — |
iconName Name of the Icon that is shown next to the label. Refer to the full list here: Link | "AddValue" | "Adjust" | "AIAgent" | "AppGrid" | "ArrowCircleLeft" | "ArrowCircleRight" | "ArrowDown"... | — |
iconAlignment Whether the icon should appear after or before the label |
| — |
aria-pressed Optional aria-pressed prop for accessibility |
| — |
isLoading Boolean prop to add a loader to the component |
| — |
strikeThrough Indicates whether the button has a strikethrough |
| — |
css Add custom styles to this component. Use with caution. Learn more here: Link |
| — |
inputFieldCss |
| — |
data-{foo} Data attributes can be used by testing libraries to retrieve components or assert their existence |
| — |
| * - the prop is required. |
RuleGroup.OrButton
| Name | Type | Default |
|---|---|---|
label Overrides the default 'AND'/'OR' label |
| — |
onMouseEnter Native HTML callback: Link |
| — |
onMouseLeave Native HTML callback: Link |
| — |
aria-label Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link |
| — |
aria-labelledby Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link |
| — |
disabled Use this prop if the component should not be interactive |
| — |
onClick Native HTML callback: Link |
| — |
id Native HTML attribute: Link . |
| — |
type Native HTML attribute: Link |
| — |
isHighlighted isHighlighted boolean for Highlighted tertiary button |
| — |
size Size of the button |
| — |
onFocus Native HTML callback: Link |
| — |
onBlur Native HTML callback: Link |
| — |
onKeyDown Native HTML callback: Link |
| — |
kind Kind of the button | "negative" | "primary" | "secondary" | "tertiary" | "dark" | "floating" | "feedback-info-primary" | ... | — |
iconName Name of the Icon that is shown next to the label. Refer to the full list here: Link | "AddValue" | "Adjust" | "AIAgent" | "AppGrid" | "ArrowCircleLeft" | "ArrowCircleRight" | "ArrowDown"... | — |
iconAlignment Whether the icon should appear after or before the label |
| — |
aria-pressed Optional aria-pressed prop for accessibility |
| — |
isLoading Boolean prop to add a loader to the component |
| — |
strikeThrough Indicates whether the button has a strikethrough |
| — |
css Add custom styles to this component. Use with caution. Learn more here: Link |
| — |
inputFieldCss |
| — |
data-{foo} Data attributes can be used by testing libraries to retrieve components or assert their existence |
| — |
| * - the prop is required. |