Skip to content

Text column

TextColumn is used to render simple text in the table — it’s the column that gets used by default when a column is not specified:

from almasix.orbit.tables import TextColumn
TextColumn.make("title")

If you only want to render the text of a column and nothing else, you don’t need to use TextColumn explicitly — passing the attribute name to .columns([...]) as a bare string is enough in most apps. TextColumn becomes useful once you reach for formatting, color, icons, badges, or any of the helpers below.

You may set a color for the text, using any of the inbuilt color namesprimary, success, warning, danger, info, or gray:

TextColumn.make("status").color("primary")

Text colors (light) Text colors (dark)

To choose the color at render time, pass a callback. It receives record and state:

TextColumn.make("priority").color(
lambda state=None, **_: "danger" if state == "urgent" else "gray",
)

Text columns can have an icon rendered next to their content:

TextColumn.make("name").icon("heroicon-o-user")

The icon defaults to the position before the text. You can move it after the text instead:

TextColumn.make("email")
.icon("heroicon-o-envelope")
.icon_position("after")

The icon’s color defaults to the color of the text, but you may customize it independently:

TextColumn.make("email")
.icon("heroicon-o-envelope")
.icon_color("primary")

Text icon before/after and icon color (light) Text icon before/after and icon color (dark)

By default, text is quite plain and has no background color. You can display it as a “badge” instead, which gives the text a pill-shaped colored background and better draws the eye to it:

TextColumn.make("status").badge().color("success")

.badge() also accepts a condition, so the same column can render as plain text or a badge depending on the row:

TextColumn.make("status").badge(
lambda record=None, **_: record.get("status") != "draft",
)

If a column is always a badge, reach for BadgeColumn instead — it’s a thin TextColumn subclass with .badge() already applied, so you don’t have to repeat it.

You can format the column’s state using .date(), its %-style strftime equivalent, .date_time(), or a time-only value with .time():

TextColumn.make("published_at").date("%b %d, %Y")
TextColumn.make("updated_at").date_time("%b %d, %Y %H:%M")
TextColumn.make("opens_at").time("%H:%M")

Each accepts datetime/date/time objects as well as ISO-formatted strings — Orbit parses the string for you before formatting it.

To display the date as relative-to-now text instead (“5 minutes ago”, “in 2 days”), use .since():

TextColumn.make("last_seen_at").since()

.numeric() formats the column as a number, optionally rounding to a fixed number of decimal places:

TextColumn.make("views").numeric()
TextColumn.make("score").numeric(decimal_places=1).align_end()

.money() formats the state as currency. Pass the currency code as the first argument:

TextColumn.make("amount").money("USD")

If your database stores the value as an integer in cents (or another minor unit) to avoid rounding errors, use divide_by to turn the integer into major units before formatting:

TextColumn.make("amount_cents").money("USD", divide_by=100)

decimal_places controls how many digits render after the decimal point (defaults to 2):

TextColumn.make("amount").money("EUR", decimal_places=0)

Money columns (light) Money columns (dark)

.format_state_using() lets you transform the resolved state into anything you like, right before it renders — the raw value is still used for sorting and searching:

TextColumn.make("sku").format_state_using(lambda state: state.upper())

If your text column contains Markdown, you may render it with .markdown(). Orbit supports a small, safe subset — **bold**, *italic*, and newlines:

TextColumn.make("blurb").markdown()

If the state already contains trusted HTML, use .html() to render it as-is instead of escaping it:

TextColumn.make("summary").html()

Only enable .html() for content you control — Orbit does not sanitize it.

Descriptions allow you to render extra text below (or above) the column’s contents:

TextColumn.make("title").description(
lambda record=None, **_: record.get("subtitle", ""),
)

By default the description is placed below the main text. Use position="above" to render it first:

TextColumn.make("title").description("Draft — not yet published", position="above")

If a column’s state is naturally a list — or a delimited string — you can display each item on its own line, or split a string into that list.

Render list state with a bullet in front of each item using .bulleted():

TextColumn.make("features").bulleted()

If the state is a plain string, .separator() splits it before display. Pair it with .bulleted() for a bulleted list, or leave it plain to display the pieces on one line, rejoined by the same separator:

TextColumn.make("tags").separator(",")
TextColumn.make("tags").separator(",").bulleted()

Combine .separator() with .badge() to turn a CSV string or list into a cluster of badges, right inside TextColumn:

TextColumn.make("tags").separator(",").badge().color("primary")

Text defaults to a normal font size. You can choose a size, from xs through 2xl:

TextColumn.make("heading").size("lg")

Text defaults to a normal font weight. Use a heavier or lighter weight — thin, light, medium, semibold, bold, extrabold, or black — to draw attention to important columns:

TextColumn.make("name").weight("bold")

You may switch to a serif or monospaced font for identifiers, code, or amounts:

TextColumn.make("reference").font_family("mono")

Text size, weight, and font family (light) Text size, weight, and font family (dark)

.limit() truncates the text to a set number of characters, appending an ellipsis (or a custom string) when clipped:

TextColumn.make("description").limit(50)
TextColumn.make("description").limit(50, end=" (…)")

To truncate by word count instead of character count, use .words():

TextColumn.make("description").words(10)

By default, cells clip long text on one line. .wrap() allows it to wrap onto multiple lines instead of truncating:

TextColumn.make("description").wrap()

.line_clamp() lets text wrap, but only up to a fixed number of lines before it is clipped with an ellipsis — useful for long descriptions in a fixed-height row:

TextColumn.make("description").wrap().line_clamp(2)

You may make the text copyable, so an operator can click a button to copy the value to their clipboard, using .copyable():

TextColumn.make("api_token").copyable()

You can override the tooltip that appears once the value is copied, and how long it appears for:

TextColumn.make("api_token")
.copyable()
.copy_message("Copied to clipboard")
.copy_message_duration(1500)

You may open a URL when a cell is clicked, either in the same browser tab, or a new one:

TextColumn.make("title").url(
lambda record=None, **_: f"/posts/{record['id']}",
)
TextColumn.make("website").url(
lambda state=None, **_: state,
).open_url_in_new_tab()
from almasix.orbit.tables import Table, TextColumn
Table.make("orders").columns([
TextColumn.make("title")
.weight("bold")
.description(lambda record=None, **_: record.get("customer", "")),
TextColumn.make("status").badge().color(
lambda state=None, **_: {"paid": "success", "due": "warning"}.get(state, "gray"),
),
TextColumn.make("amount").money("USD").align_end().sortable(),
TextColumn.make("placed_at").since(),
TextColumn.make("reference").copyable().font_family("mono"),
]).records([
{
"id": 1,
"title": "#1042",
"customer": "Ada Lovelace",
"status": "paid",
"amount": 4899,
"placed_at": "2026-09-18T10:15:00",
"reference": "ORB-1042",
},
{
"id": 2,
"title": "#1043",
"customer": "Grace Hopper",
"status": "due",
"amount": 1200,
"placed_at": "2026-09-17T08:00:00",
"reference": "ORB-1043",
},
])
Method Effect
.color(str | callable) Text color — primary, success, warning, danger, info, gray
.icon(str | callable) / .icon_position("before" | "after") / .icon_color(...) Icon next to the text
.badge(bool | callable) Pill-shaped background
.date(fmt) / .date_time(fmt) / .time(fmt) / .since() Date & time formatting
.numeric(decimal_places=None) Number formatting
.money(currency, *, divide_by=1, decimal_places=2) Currency formatting
.markdown() / .html() Rich content rendering
.description(text, *, position="below" | "above") Secondary text
.bulleted() / .separator(char) List rendering / splitting
.size(...) / .weight(...) / .font_family(...) Typography
.limit(n, end="…") / .words(n) / .wrap() / .line_clamp(n) Truncation & wrapping
.copyable() / .copy_message(...) / .copy_message_duration(ms) Clipboard
.url(str | callable) / .open_url_in_new_tab() Links
.format_state_using(callback) Transform the display value only

See Columns overview for the shared APIs every column type gets for free — state, sorting, searching, tooltips, visibility, and more.