UI for constructing complex queries.
Status is "Open" AND Priority is "High"
<div class="qbuilder" style="--accent:#D9C8F0;">
<div class="qbuilder__row">
<select class="qbuilder__select" aria-label="Field">
<option>Status</option>
<option>Owner</option>
<option>Priority</option>
</select>
<select class="qbuilder__select" aria-label="Operator">
<option>is</option>
<option>is not</option>
<option>contains</option>
</select>
<input class="qbuilder__input" type="text" aria-label="Value" value="Open">
</div>
<div class="qbuilder__joiner">
<select class="qbuilder__select qbuilder__select--joiner" aria-label="Match logic">
<option>AND</option>
<option>OR</option>
</select>
</div>
<div class="qbuilder__row">
<select class="qbuilder__select" aria-label="Field" disabled>
<option>Priority</option>
</select>
<select class="qbuilder__select" aria-label="Operator" disabled>
<option>is</option>
</select>
<input class="qbuilder__input" type="text" aria-label="Value" value="High" disabled>
</div>
<p class="qbuilder__preview" role="status">Status is "Open" AND Priority is "High"</p>
</div>
<style>
.qbuilder { font: 14px system-ui, sans-serif; color: #241430; max-width: 420px; }
.qbuilder__row { display: flex; gap: 8px; margin-bottom: 8px; }
.qbuilder__select, .qbuilder__input { box-sizing: border-box; padding: 8px 10px; font: inherit; color: #241430; background: #FAF7F2; border: 1.5px solid #24143040; appearance: none; }
.qbuilder__select { flex: 0 0 auto; }
.qbuilder__input { flex: 1; }
.qbuilder__select:focus-visible, .qbuilder__input:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent); }
.qbuilder__joiner { display: flex; justify-content: center; margin-bottom: 8px; }
.qbuilder__select--joiner { font-weight: 600; background: var(--accent); border-color: transparent; }
.qbuilder__row:last-of-type .qbuilder__select, .qbuilder__row:last-of-type .qbuilder__input { opacity: .55; }
.qbuilder__preview { margin: 10px 0 0; padding: 10px 12px; background: #24143010; font-size: 13px; }
</style><div class="font-sans text-sm text-[#241430] max-w-[420px]" style="--accent:#D9C8F0;">
<div class="flex gap-2 mb-2">
<select aria-label="Field" class="box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040] appearance-none focus-visible:outline-none focus-visible:border-[var(--accent)]">
<option>Status</option>
<option>Owner</option>
<option>Priority</option>
</select>
<select aria-label="Operator" class="box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040] appearance-none focus-visible:outline-none focus-visible:border-[var(--accent)]">
<option>is</option>
<option>is not</option>
<option>contains</option>
</select>
<input type="text" aria-label="Value" value="Open" class="flex-1 box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040] focus-visible:outline-none focus-visible:border-[var(--accent)]">
</div>
<div class="flex justify-center mb-2">
<select aria-label="Match logic" class="px-2.5 py-2 font-semibold text-[#241430] bg-[var(--accent)] border-[1.5px] border-transparent appearance-none">
<option>AND</option>
<option>OR</option>
</select>
</div>
<div class="flex gap-2 mb-2 opacity-55">
<select aria-label="Field" disabled class="box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040] appearance-none">
<option>Priority</option>
</select>
<select aria-label="Operator" disabled class="box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040] appearance-none">
<option>is</option>
</select>
<input type="text" aria-label="Value" value="High" disabled class="flex-1 box-border px-2.5 py-2 text-[#241430] bg-[#FAF7F2] border-[1.5px] border-[#24143040]">
</div>
<p role="status" class="mt-2.5 px-3 py-2.5 bg-[#24143010] text-[13px]">Status is "Open" AND Priority is "High"</p>
</div>export function QueryBuilder({
field = "Status",
operator = "is",
value = "Open",
logic = "AND",
accent = "#D9C8F0",
}) {
const selectStyle = { boxSizing: "border-box", padding: "8px 10px", color: "#241430", background: "#FAF7F2", border: "1.5px solid #24143040", appearance: "none" };
const inputStyle = { flex: 1, boxSizing: "border-box", padding: "8px 10px", color: "#241430", background: "#FAF7F2", border: "1.5px solid #24143040" };
return (
<div style={{ fontFamily: "system-ui, sans-serif", fontSize: 14, color: "#241430", maxWidth: 420, "--accent": accent }}>
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
<select aria-label="Field" style={selectStyle} defaultValue={field}>
<option>Status</option>
<option>Owner</option>
<option>Priority</option>
</select>
<select aria-label="Operator" style={selectStyle} defaultValue={operator}>
<option>is</option>
<option>is not</option>
<option>contains</option>
</select>
<input type="text" aria-label="Value" defaultValue={value} style={inputStyle} />
</div>
<div style={{ display: "flex", justifyContent: "center", marginBottom: 8 }}>
<select aria-label="Match logic" style={{ ...selectStyle, fontWeight: 600, background: accent, borderColor: "transparent" }} defaultValue={logic}>
<option>AND</option>
<option>OR</option>
</select>
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 8, opacity: 0.55 }}>
<select aria-label="Field" disabled style={selectStyle}>
<option>Priority</option>
</select>
<select aria-label="Operator" disabled style={selectStyle}>
<option>is</option>
</select>
<input type="text" aria-label="Value" defaultValue="High" disabled style={inputStyle} />
</div>
<p role="status" style={{ margin: "10px 0 0", padding: "10px 12px", background: "#24143010", fontSize: 13 }}>
{field} {operator} "{value}" {logic} Priority is "High"
</p>
</div>
);
}