Rule Builder

Pro

Write a rule in plain words and read it back as a sentence of editable conditions, with its action and a backtest of what it would have done.

Office supplies from contracted suppliers

DraftVersion 3
Enter to read the rule
When a requestwhereandandthen

Backtest

Last 90 days, 1,480 requests

Matches
312
of 1,480
Share
21%
Amount
€40.9k
Hours saved
87 h
Disputed later
7
Requests the rule would match, by week
weekMatchedStill to a person
W12697
W23192
W32598
W422101
W52895
W63093
W723100
W823100
W93093
W102895
W1122101
W122598

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Activate your Pro license (once per machine)npx koalaui-cli@latest login <key># Add this component (its dependencies come along)npx koalaui-cli@latest add rule-builder

Usage#

The builder parses nothing itself. Hand the text to your own parser in onSubmit, set pending while it runs, and draw what comes back. Keep evaluating rules in deterministic code: the builder only shows them.

This example is part of Koala UI Pro

The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.

Have a license? Unlock it here

Anatomy#

import {  RuleBuilder,  RuleBuilderHeader,  RuleBuilderTitle,  RuleBuilderMeta,  RuleStatus,  RuleVersion,  RuleBuilderInput,  RuleSentence,  RuleText,  RuleClause,  RuleCondition,  RuleAction,  RuleAddCondition,  RuleMissing,  RulePreview,  RulePreviewHeader,  RulePreviewTitle,  RulePreviewDescription,  RuleStats,  RuleStat,  RuleBuilderFooter,} from "@/components/ui/rule-builder"

From a parser#

Pass the parser's tokens straight to tokens. Each connector stays on the line of the chip after it, and removing a chip moves focus to the next one.

When an invoicewhereMatchismatched or withinandAmountis under€5kandSupplier is contractedthenapprove automatically

States#

A guessed condition takes a dashed edge, the pieces the parser missed are listed under the sentence, and while the rule is being read the sentence dims in place instead of disappearing.

Partly understood

Draft
When an invoicewhereAmountis over€10kandSupplierisnewthenrequire the CFO
Not understood yet:
  • "during month end"

Not understood

Off
Not understood yet:
  • a condition
  • what should happen
Try naming an amount, a category or a supplier, and an outcome.

Reading

ShadowVersion 2
When an invoicewhereSupplierisnewthenhold for review

Action tones#

The action is the one filled chip in the sentence. Keep it in ink, or give an outcome its status color.

thenrequire the budget owner
thenapprove automatically
thenhold for review
thenreject

Status and version#

ActiveShadowDraftOffVersion 3

API reference#

RuleBuilder

The frame and the context every part reads. Every part accepts className, merged last.

PropTypeDefaultDescription
pendingbooleanfalseThe rule is being read. The submit button spins, and the sentence and the backtest dim until you clear it.
variant"card" | "plain""card"A framed surface, or no chrome for a full page.
density"comfortable" | "compact"-Padding and spacing between parts. Reads DensityProvider when unset.
Data attributeDescription
data-pendingPresent while the rule is being read.

RuleBuilderInput

The plain-language field, built on PromptInput. Enter submits, Shift+Enter breaks a line, and the text stays after you submit. Children are a hint at the start of its toolbar.

PropTypeDefaultDescription
onSubmit(text: string) => void-Read the text with your parser and set pending while it runs.
valuestring-The text, when you control it.
defaultValuestring""The text on the first render.
onValueChange(text: string) => void-The text changed.
pendingboolean-Overrides the pending of RuleBuilder.
placeholderstring"Describe the rule in plain words"Shown while the field is empty.
labelstring"Rule"Names the field for a screen reader.
submitLabelstring"Read rule"Names the submit button.

RuleSentence

The compiled rule as a line of text with chips in it. Compose RuleText, RuleClause, RuleCondition and RuleAction inside it, or pass tokens.

PropTypeDefaultDescription
tokensreadonly RuleToken[]-Draws the sentence from a parser's output: { kind: "text", text }, { kind: "condition", index, field, op, value, uncertain? } or { kind: "action", text }. Each connector is kept on the line of the chip after it.
renderEditor(token: RuleChipToken) => ReactNode-The editor for a condition or the action, when you pass tokens.
onRemoveCondition(index: number) => void-Shows a remove button on each condition, when you pass tokens.
addConditionReactNode-Placed after the last condition, usually a RuleAddCondition.
actionTone"neutral" | "success" | "warning" | "destructive"-The tone of the action chip, when you pass tokens.
Data attributeDescription
data-pendingPresent while the rule is being read.

RuleClause

Keeps a connector such as where, and or then on one line with the chip that follows it, so a line never ends on a lone word.

RuleCondition

One condition as a chip: the field, the operator and the value. With an editor it is a button that opens a popover.

PropTypeDefaultDescription
field*string-What is tested, such as Amount.
operatorstring-How, such as is under.
valuestring-Against what, such as €300. Set in tabular numerals.
uncertainbooleanfalseThe parser guessed. The chip takes a dashed warning edge and says so to a screen reader.
onRemove() => void-Shows the remove button. Focus moves to the next chip.
editorReactNode-What the chip's popover holds, usually a Select for the field and the operator and an input for the value. Without it the chip is plain text.
openboolean-Whether the editor is open, when you control it.
defaultOpenbooleanfalseOpens the editor on the first render.
onOpenChange(open: boolean) => void-The editor opened or closed.
editorClassNamestring-Merged onto the popover panel, to size it.
editLabelstring"Edit condition"Starts the button's accessible name.
removeLabelstring"Remove condition"Starts the remove button's accessible name.
uncertainLabelstring"not sure"Read after an uncertain condition.
Data attributeDescription
data-uncertainPresent on a guessed condition.

RuleAction

What the rule does, the then of the sentence, as a filled chip. Children are its label.

PropTypeDefaultDescription
tone"neutral" | "success" | "warning" | "destructive""neutral"Ink for the default, or a status color for an outcome you want to stand out.
editorReactNode-What the chip's popover holds, usually a Select for the field and the operator and an input for the value. Without it the chip is plain text.
openboolean-Whether the editor is open, when you control it.
defaultOpenbooleanfalseOpens the editor on the first render.
onOpenChange(open: boolean) => void-The editor opened or closed.
editorClassNamestring-Merged onto the popover panel, to size it.
editLabelstring"Edit action"Starts the button's accessible name.
Data attributeDescription
data-toneThe tone.

RuleAddCondition

A dashed chip that adds a condition. Handle onClick, or pass an editor to open a popover from it.

PropTypeDefaultDescription
editorReactNode-What the chip's popover holds, usually a Select for the field and the operator and an input for the value. Without it the chip is plain text.
openboolean-Whether the editor is open, when you control it.
defaultOpenbooleanfalseOpens the editor on the first render.
onOpenChange(open: boolean) => void-The editor opened or closed.
editorClassNamestring-Merged onto the popover panel, to size it.
childrenReactNode"Condition"The label. Without it the button is named Add condition.

RuleMissing

A quiet warning that lists what the parser could not read. It renders nothing when the list is empty.

PropTypeDefaultDescription
itemsreadonly ReactNode[][]The pieces that were not understood.
labelReactNode"Not understood yet:"Leads the list.
childrenReactNode-A hint after the list.

RulePreview

The backtest under the sentence. Holds RulePreviewHeader, RuleStats and anything else, such as a Chart. Dims while the rule is read.

PropTypeDefaultDescription
aria-labelstring"Backtest"Names the region.

RuleStat

One figure of RuleStats, a description list that sits on one row when there is room and on two columns when there is not.

PropTypeDefaultDescription
label*ReactNode-What the figure counts.
value*ReactNode-The figure, in tabular numerals.
detailReactNode-A small line under the figure, such as of 1,480.
tone"neutral" | "success" | "warning" | "destructive""neutral"Colors the figure, such as warning for false positives.
childrenReactNode-Anything under the figure, such as a Meter.

RuleStatus

The state of the rule as a Badge with a dot. Children replace the label.

PropTypeDefaultDescription
status*"active" | "shadow" | "draft" | "off"-Sets the color and the default label.

RuleVersion

The version as a Badge, read out in full by a screen reader.

PropTypeDefaultDescription
version*number-Shown as v3.
labelstring"Version"What a screen reader says before the number.

RuleBuilderFooter

The row of actions. It stacks on a phone with the last button on top, and sits right-aligned from sm up.

FAQ#

No. It draws what your parser returns, from a few regular expressions or a language model. That keeps the rule you save reproducible and auditable.