Align text in fixed-width columns for monospace display
Turn structured rows of text into clean, fixed-width columns without manually counting spaces.
Run — free
Runs in your browser. Free, unlimited — your data never leaves this page.
Supply the rows and the expected number of columns, and the formatter measures the widest value in each position before padding every field to that width. The result includes reusable column widths, padded fields, and a ready-to-display multiline string. Inconsistent rows are rejected instead of producing ambiguous output, making the capability useful for logs, command-line reports, plain-text emails, generated documentation, and any other destination where a monospace layout matters.
Prepare rows with a clear column contract
Start with a list of rows, where each row is itself a list of text fields. Set column_count to the exact number of fields that every row should contain. A header can be the first row, but it is not required or treated specially; it participates in width calculation just like every other value. Preserve the text exactly as you want it displayed, including capitalization and intentional internal spaces. Empty strings are valid fields and will be padded to the width established by other values in the same column. The explicit count is an important safety check when rows are assembled from different sources. If one row contains too few or too many fields, the request fails with an input error that identifies the row and its actual field count. That prevents a missing value from silently shifting every later field into the wrong visual column. For predictable output, normalize tabs or line breaks inside individual fields before sending them, because this formatter pads text but does not reinterpret control characters or split one field into multiple display lines.
Understand width measurement and padding
The formatter examines each column independently and records the largest character count found in that position. It then appends ordinary space characters to every field until the field reaches its column's recorded width. One separator space is inserted between adjacent padded fields, including after a column that is already at maximum width. Width is measured by Unicode code points rather than JavaScript UTF-16 storage units, so common supplementary characters are not accidentally counted twice. The calculation deliberately avoids terminal-specific assumptions about combining marks, tabs, emoji presentation, and East Asian wide characters. Those can occupy different screen widths depending on the font and renderer, while the capability promises a stable deterministic transformation. For ordinary letters, numbers, punctuation, and spaces in a monospace font, the resulting lines align consistently. The last field is padded too, because the stated operation applies the width contract to every field and because downstream code may rely on equal field widths rather than only the appearance of the joined string. The response exposes both the padded field arrays and the joined text, so callers do not need to parse formatted lines to recover structure.
Use the result in reliable text workflows
Choose the output form that matches the next step. Use aligned_text when you need a complete block for a console, a code fence, a plain-text report, or a message rendered with a monospace font. Use aligned_rows when you want to add prefixes, borders, indentation, colors, or other presentation around individual fields while preserving the computed padding. The column_widths array is useful when a second system must format additional rows with the same dimensions, and the returned row and column counts make basic auditing straightforward. The transformation is pure: it does not trim values, sort rows, infer headers, truncate long content, or change case. That restraint makes it safe for identifiers and data whose whitespace is meaningful. Requests are bounded by the published row, column, and per-field character limits so accidental oversized input fails clearly rather than consuming unbounded memory. Because there are no network calls, random choices, clocks, or locale-sensitive comparisons, identical JSON input always produces identical JSON output. API automation costs $0.002 per request, while the browser runner can apply the same algorithm interactively.
What you can do with it
Format command-line reports
Align names, statuses, durations, and identifiers into scan-friendly columns before writing a monospace console report.
Build plain-text summaries
Create readable tables for email, tickets, documentation, or chat systems that cannot depend on HTML table markup.
Validate row-shaped exports
Catch missing or surplus fields while producing a stable aligned preview of generated records.
FAQ
What does a request cost?
Each API request costs $0.002. The page can also run the formatter locally in the browser.
What happens when a row has the wrong number of fields?
The request fails with an invalid input error identifying the first inconsistent row, its field count, and the expected count.
Are values trimmed or otherwise changed?
No. Original text is preserved, and only trailing padding spaces plus separator spaces are added.
Does it support Unicode text?
Yes. Width uses Unicode code points, although renderer-specific wide or combining glyph behavior is not estimated.
Is the final column padded?
Yes. Every field is padded to the widest value in its column, including the last field in each row.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/text/text-column-align \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"rows":[["Name","Role","City"],["Ada","Engineer","London"],["Grace Hopper","Admiral","New York"]],"column_count":3}'const res = await fetch("https://api.kit.forhosting.com/text/text-column-align", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"rows": [
[
"Name",
"Role",
"City"
],
[
"Ada",
"Engineer",
"London"
],
[
"Grace Hopper",
"Admiral",
"New York"
]
],
"column_count": 3
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/text/text-column-align",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"rows": [
[
"Name",
"Role",
"City"
],
[
"Ada",
"Engineer",
"London"
],
[
"Grace Hopper",
"Admiral",
"New York"
]
],
"column_count": 3
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/text/text-column-align", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"rows":[["Name","Role","City"],["Ada","Engineer","London"],["Grace Hopper","Admiral","New York"]],"column_count":3}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"rows":[["Name","Role","City"],["Ada","Engineer","London"],["Grace Hopper","Admiral","New York"]],"column_count":3}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/text/text-column-align", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"rows": [
[
"Name",
"Role",
"City"
],
[
"Ada",
"Engineer",
"London"
],
[
"Grace Hopper",
"Admiral",
"New York"
]
],
"column_count": 3
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "text.text_column_align",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_rows | 10000 |
max_columns | 100 |
max_field_chars | 10000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |