ConditionEditor

GithubStorybook

The ConditionEditor components lay out editable condition rows — a card per condition holding the controls that edit it, joined to the rows around it by their operator.

Anatomy

[WIP image]

Anatomy elements
#RequiredElementDescription
1SetA stack of conditions joined by their operators.
2NoConnectorThe line down the left edge joining a set of two or more conditions.
3NoOperatorThe badge on the connector between two conditions, saying how they combine.
4NoHandleThe reorder button, sitting on the connector beside its card.
5CardOne condition, boxed.
6NoHeadingAn optional caption above the controls naming what kind of condition the card is.
7ControlsThe first line of the card: the fields that edit the condition.
8NoDetailsContent stacked below the controls — refinements, filter rows, a way to add one.
9NoCard footerAn optional line at the foot of the card for whatever the row needs, such as an example of what it matches.
10NoActionsRow-level buttons at the trailing edge of the card, such as duplicate and remove.
11NoNested groupA set inside a condition, drawn as a box of its own.
12NoSet headerAn optional line above a set's rows saying how they combine, with the set's own actions.
13NoSet footerAn optional line below a set's rows, such as the way to add a condition.

Guidelines

ConditionEditor vs. ConditionViewer

  • ConditionEditor lays out conditions a person is building or changing, one card of controls per rule.
  • ConditionViewer summarizes a finished rule set as sentences. Reach for it wherever the rules are read rather than changed.

The two share their surfaces and nesting treatment, so moving between viewing and editing a set keeps its shape. Pair them: a summary in ConditionViewer with an action into the editor.

Release status

ConditionEditor is in alpha. Expect its API and visual details — the operator, the connector, where the handle sits — to change between minor releases.

What the application provides

These components lay out the condition rows, and a set's header and footer around them. What goes in those — the control that sets how a group matches, the way to add a condition or group, the operator wording — and validation and drag-and-drop behavior all belong to the application.

Best practices

Accessible names

Give every control in a row an accessible name. A condition row has no visible field labels, so without one a screen reader announces a row of unnamed dropdowns.

Do: Name each control for what it edits.
Don't: Add visible labels above each control to name them.

Controls

Pass the controls straight into the item, which lays them out as one line. Wrapping them in an InlineStack throws off their spacing and alignment.

Do: Pass the controls directly to the item.
Don't: Wrap the controls in an InlineStack.

Details

Put anything that refines a condition below its first line, in the item's details — extra filter rows, a frequency, an "Add filter" button. Keep the first line to the controls that define the rule, so a set of rows stays scannable down its left edge.

performed
Do: Stack refinements in the details below the main controls.
performed
Don't: Append refinements to the end of the first line.

Headings

Name what kind of condition a card is in its heading — "Object attribute", "Relationship attribute" — when a set mixes kinds. Let the field picker decide the kind, so changing the field relabels the card; don't add a separate type control for it.

Object attribute
Do: Name the kind in the heading and let the field pick it.
Don't: Spend the first line on a type control when the field already says the kind.

Card footers

Use a card's footer for what helps read the condition: an example record it would match ("e.g. Ada Lovelace’s plan is pro"), a link to the people it matches. Keep it to one short line in small secondary text, and update it as the controls change. Don't put errors there; a control shows its own.

Row actions

Put row-level actions such as duplicate and remove in the item's actions, at the trailing edge of the card. They stay lined up down the set however long each row's controls run.

Do: Place duplicate and remove in the card's actions.
Don't: Mix row actions in among the controls.

Sub-groups

Build a sub-group by nesting a ConditionEditor inside an item. The nested set is drawn as a group, so depth reads the same way it does in ConditionViewer. A group inside a group takes the other surface, so each level reads as its own box, and when a group has a header the connector meets it there, at the line that names the group. Avoid drawing your own border or background to fake a group.

And
Or
Do: Nest a ConditionEditor inside an item for a sub-group.

Types

Single condition

One card with no connector. Use it where a rule set starts empty and the first condition has just been added.

And
Set of conditions

The common case: two or more cards joined by a connector, with an operator between each pair.

And
Or
Nested group

A set inside a condition, for rules that combine differently from the set around them.

Appearance

Cards

Each condition is a card, matching ConditionViewer's conditions. A nested group is drawn as a box of its own in place of its condition's card.

Connector

A set of two or more conditions is joined by a connector down its left edge. A single condition has no connector, since there is nothing to join.

Single condition

No connector. The card sits on its own.

Or
Two or more conditions

The connector runs down the set and through each operator.

Handle

A handle sits on the connector beside its card. Show handles on every row of a set or on none — a set where only some rows can move reads as broken.

And
With handles

Use it where the order of conditions can be changed.

And
Without handles

Use it where order doesn't matter or can't be changed.

Operators

Every condition after the first carries an operator, shown as a Badge between it and the condition above. Leave the first condition without one; there is nothing above it to join.

When a set has one operator, let people change it in one place: the set's header, as an All / Any SegmentedControl, with every operator between its conditions a read-only label. A control between two conditions reads as joining just those two, so don't make one of them change the others. Any condition's operator can still be switchable on its own, where each really is independent; then a set can mix operators, so nest the part that binds tighter as a group to keep the reading clear.

Behaviors

States

The handle can be disabled, for example while a set is saving or when a person lacks permission to reorder it.

Base

The handle at rest.

Disabled

Reordering is unavailable for this row.

Alignment

Every form control and Button in a row lines up on the same center line, so the row stays level when one control shows an error and its neighbors don't.

exists
Row with an error

The error sits below its own control; the rest of the row stays on the same line.

Dragging

The handle is a ready-made reorder button, not a drag-and-drop implementation. Connect it to the drag-and-drop library the application already uses, which also provides keyboard reordering.

Wrapping

The first line of controls wraps when the card runs out of width, and the actions stay at the trailing edge. Keep a row's controls few enough that it fits on one line at common widths; a condition that wraps every time is a sign it needs details below it instead.

Content

Accessible names

Name each control for the part of the rule it edits — Attribute, Operator, Value, Event name. Name icon-only actions for what they do to the row: Duplicate condition, Remove condition. The handle is always named Reorder condition.

Remove condition
Do: Name the action and the thing it acts on.
Close
Don't: Use a bare verb or the icon's name.

Operators

Write operators in sentence case — And, Or — as all Badge content is. Use one short word; the badge reads between two cards, not as part of a sentence.

And
Do: Use sentence case.
AND
Don't: Use all caps or lowercase.