typed-xlsx
Columns

Badges and Checkboxes

Render status badges and boolean checkbox glyphs from typed schema columns.

Status columns and boolean columns almost always end up hand-styled: one color per status value, a / mapping for booleans, repeated in every export that touches the field. Badge and checkbox columns move that mapping into the schema, next to the column it belongs to.

type: "badge" and type: "checkbox" write ordinary cell values with generated styles — no embedded objects, no compatibility surprises. They work in buffered and streaming exports, including report-mode sub-row expansion.

Badge columns

Badge columns are useful for status, tier, priority, and workflow-state fields:

import { 
createExcelSchema
} from "typed-xlsx";
type
Product
= {
sku
: string;
status
: "Live" | "Low stock" | "Launch";
}; const
schema
=
createExcelSchema
<
Product
>()
.
column
("sku", {
accessor
: "sku",
}) .
column
("status", {
type
: "badge",
accessor
: "status",
variants
: {
Live
: {
style
: {
fill
: {
color
: {
rgb
: "DCFCE7" } },
font
: {
color
: {
rgb
: "166534" },
bold
: true },
}, }, "Low stock": {
style
: {
fill
: {
color
: {
rgb
: "FEF3C7" } },
font
: {
color
: {
rgb
: "92400E" },
bold
: true },
}, },
Launch
: {
label
: "New",
style
: {
fill
: {
color
: {
rgb
: "DBEAFE" } },
font
: {
color
: {
rgb
: "1D4ED8" },
bold
: true },
}, }, }, }) .
build
();

Each variants key is matched against String(value). A variant can be a full CellStyle, or { label, style } when the displayed text should differ from the source value — above, Launch rows display as New.

When the accessor value is a literal union, variant keys are type-checked against that union. Without defaultVariant, every union member must have a variant. With defaultVariant, variants can cover only the special cases and the fallback handles the rest:

import { 
createExcelSchema
} from "typed-xlsx";
type
Ticket
= {
status
: "New" | "In progress" | "Blocked" | "Done";
}; const
schema
=
createExcelSchema
<
Ticket
>()
.
column
("status", {
type
: "badge",
accessor
: "status",
variants
: {
Blocked
: {
style
: {
fill
: {
color
: {
rgb
: "FEE2E2" } },
font
: {
color
: {
rgb
: "991B1B" },
bold
: true },
}, },
Done
: {
style
: {
fill
: {
color
: {
rgb
: "DCFCE7" } },
font
: {
color
: {
rgb
: "166534" },
bold
: true },
}, }, },
defaultVariant
: {
style
: {
fill
: {
color
: {
rgb
: "F1F5F9" } },
font
: {
color
: {
rgb
: "475569" } },
}, }, }) .
build
();

Fallback styling

When the source field is an open string (statuses coming from an API, user-defined tags), unknown values would render unstyled. Use defaultVariant to give them one shared fallback:

import { 
createExcelSchema
} from "typed-xlsx";
type
Ticket
= {
id
: string;
priority
: string; // open set — new priorities may appear
}; const
schema
=
createExcelSchema
<
Ticket
>()
.
column
("id", {
accessor
: "id" })
.
column
("priority", {
type
: "badge",
accessor
: "priority",
variants
: {
Critical
: {
style
: {
fill
: {
color
: {
rgb
: "FEE2E2" } },
font
: {
color
: {
rgb
: "991B1B" },
bold
: true },
}, },
High
: {
style
: {
fill
: {
color
: {
rgb
: "FEF3C7" } },
font
: {
color
: {
rgb
: "92400E" },
bold
: true },
}, }, },
defaultVariant
: {
style
: {
fill
: {
color
: {
rgb
: "F1F5F9" } },
font
: {
color
: {
rgb
: "475569" } },
}, }, }) .
build
();

Known priorities get their declared colors; anything else renders with the neutral fallback instead of plain text.

Checkbox columns

Checkbox columns render boolean values as centered checkbox glyphs while keeping the underlying cell value formula-friendly:

import { 
createExcelSchema
} from "typed-xlsx";
type
Task
= {
title
: string;
done
: boolean;
}; const
schema
=
createExcelSchema
<
Task
>()
.
column
("done", {
type
: "checkbox",
accessor
: "done",
}) .
column
("title", {
accessor
: "title",
}) .
build
();

By default, checked values render as , unchecked values render as , and null or undefined renders as an empty cell. Under the hood, checked cells store 1 and unchecked cells store 0; the glyphs come from the generated cell format.

That means later formula columns can reference checkbox columns directly:

import { 
createExcelSchema
} from "typed-xlsx";
type
Task
= {
title
: string;
done
: boolean;
}; const
schema
=
createExcelSchema
<
Task
>()
.
column
("done", {
type
: "checkbox",
accessor
: "done",
}) .
column
("state", {
formula
: ({
refs
,
fx
}) =>
fx
.
if
(
refs
.
column
("done").
eq
(1), "Done", "Open"),
}) .
column
("title", {
accessor
: "title",
}) .
build
();

In Excel this behaves like IF(A2=1, "Done", "Open"); SUM, COUNTIF, and comparisons also work because the cell stores numeric truthy/falsey values instead of literal glyph text.

Override the labels when a workbook needs different display text:

import { 
createExcelSchema
} from "typed-xlsx";
type
Task
= {
title
: string;
done
: boolean | null;
}; const
schema
=
createExcelSchema
<
Task
>()
.
column
("done", {
type
: "checkbox",
accessor
: "done",
checkedLabel
: "Yes",
uncheckedLabel
: "No",
emptyLabel
: "N/A",
}) .
column
("title", {
accessor
: "title",
}) .
build
();

Checkbox columns are display renderers, not interactive Excel form controls. They produce portable numeric cell values and styles, so unsupported spreadsheet clients still preserve the truthy/falsey data even if they ignore the display format.

Sub-row Expansion

Both renderers support array-valued accessors in report mode:

import { 
createExcelSchema
} from "typed-xlsx";
type
Order
= {
orderId
: string;
lines
:
Array
<{
sku
: string;
shipped
: boolean }>;
}; const
schema
=
createExcelSchema
<
Order
>()
.
column
("orderId", {
accessor
: "orderId",
}) .
column
("sku", {
accessor
: (
row
) =>
row
.
lines
.
map
((
line
) =>
line
.
sku
),
}) .
column
("shipped", {
type
: "checkbox",
accessor
: (
row
) =>
row
.
lines
.
map
((
line
) =>
line
.
shipped
),
}) .
build
();

The checkbox values expand alongside the other array-valued columns.

Copyright © 2026 Cyprien Thao. Released under the MIT License.