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.