Skip to main content

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 items
  • RuleGroup.Separator - a horizontal line with an "OR" label and icon
  • RuleGroup.AndButton - a button that adds another item to a group
  • RuleGroup.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

Live Editor
<RuleGroup.Panel>
  <div>Revenue &gt; $100</div>
  <RuleGroup.AndButton onClick={() => {}} />
</RuleGroup.Panel>
Result
Loading...

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.

Live Editor
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>
  );
}
Result
Loading...

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.

Live Editor
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>
  );
}
Result
Loading...

Usage Rules

Do:

  • Assemble Panel, Separator, AndButton and OrButton in whatever order and location fits your form
  • Override the default AND/OR label when it helps clarify what a button adds (e.g. "Add condition")
  • Let the visible label drive the accessible name - avoid passing an aria-label that doesn't include the visible text

Don't:

  • Assume Panel, Separator or 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 iconName and label are configurable

Props

RuleGroup.Panel

NameTypeDefault
children
The content of the group panel - typically a set of AND-ed rows and an AndButton
ReactNode
css
Add custom styles to this component. Use with caution. Learn more here: Link
SupportedStyleAttributes
inputFieldCss
SupportedStyleAttributes
data-{foo}
Data attributes can be used by testing libraries to retrieve components or assert their existence
string
* - the prop is required.

RuleGroup.Separator

NameTypeDefault
label
The label rendered in the center of the separator
string
"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
string
* - the prop is required.

RuleGroup.AndButton

NameTypeDefault
label
Overrides the default 'AND'/'OR' label
string
onMouseEnter
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
onMouseLeave
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
aria-label
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
aria-labelledby
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
disabled
Use this prop if the component should not be interactive
boolean
onClick
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
id
Native HTML attribute: Link .
string
type
Native HTML attribute: Link
"button" | "submit" | "reset"
isHighlighted
isHighlighted boolean for Highlighted tertiary button
boolean
size
Size of the button
"small" | "medium" | "large"
onFocus
Native HTML callback: Link
FocusEventHandler<HTMLButtonElement>
onBlur
Native HTML callback: Link
FocusEventHandler<HTMLButtonElement>
onKeyDown
Native HTML callback: Link
KeyboardEventHandler<HTMLButtonElement>
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
"left" | "right"
aria-pressed
Optional aria-pressed prop for accessibility
AriaPressed
isLoading
Boolean prop to add a loader to the component
boolean
strikeThrough
Indicates whether the button has a strikethrough
boolean
css
Add custom styles to this component. Use with caution. Learn more here: Link
SupportedStyleAttributes
inputFieldCss
SupportedStyleAttributes
data-{foo}
Data attributes can be used by testing libraries to retrieve components or assert their existence
string
* - the prop is required.

RuleGroup.OrButton

NameTypeDefault
label
Overrides the default 'AND'/'OR' label
string
onMouseEnter
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
onMouseLeave
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
aria-label
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
aria-labelledby
Provide at least one label(visual or non-visual) property for better accessibility. Check Accessibility page for more info: Link
string
disabled
Use this prop if the component should not be interactive
boolean
onClick
Native HTML callback: Link
MouseEventHandler<HTMLButtonElement>
id
Native HTML attribute: Link .
string
type
Native HTML attribute: Link
"button" | "submit" | "reset"
isHighlighted
isHighlighted boolean for Highlighted tertiary button
boolean
size
Size of the button
"small" | "medium" | "large"
onFocus
Native HTML callback: Link
FocusEventHandler<HTMLButtonElement>
onBlur
Native HTML callback: Link
FocusEventHandler<HTMLButtonElement>
onKeyDown
Native HTML callback: Link
KeyboardEventHandler<HTMLButtonElement>
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
"left" | "right"
aria-pressed
Optional aria-pressed prop for accessibility
AriaPressed
isLoading
Boolean prop to add a loader to the component
boolean
strikeThrough
Indicates whether the button has a strikethrough
boolean
css
Add custom styles to this component. Use with caution. Learn more here: Link
SupportedStyleAttributes
inputFieldCss
SupportedStyleAttributes
data-{foo}
Data attributes can be used by testing libraries to retrieve components or assert their existence
string
* - the prop is required.