I Thought Text Formatting Was Simple Until I Tried Building It With JavaScript
Why building small browser-based utilities teaches us more about Unicode, accessibility, and software design than we might expect.
There's something fascinating about modern software development.
We can now use AI to generate entire applications, build APIs, write complex SQL queries, and automate repetitive programming tasks.
Yet some of the smallest problems on the web remain surprisingly annoying.
For example:
You spend 20 minutes preparing a LinkedIn post.
You carefully structure your paragraphs, add bold headings, and make your content readable.
Then you copy it from your editor and paste it into LinkedIn.
The formatting disappears.
What seemed like a trivial problem turns out to involve rich-text formats, Unicode characters, JavaScript string handling, browser APIs, and platform-specific behavior.
And the more you investigate these problems, the more interesting they become.
This article explores the engineering concepts behind browser-based text formatting tools, why JavaScript's character counting can be misleading, and what small utilities teach us about building better software.
1. The Problem Is Not Copying Text. It's Preserving Its Representation.
Let's start with a basic example.
<p>
This is a <strong>bold</strong> statement.
</p>
In a browser, the word bold appears emphasized.
But the bold appearance comes from HTML markup, not from the letters themselves.
If we extract the text content:
const element = document.querySelector("p");
console.log(element.textContent);
The output is:
This is a bold statement.
The formatting information is gone.
Now imagine copying the same content between Google Docs, a rich-text editor, a messaging application, and a social media platform.
Different applications may support different combinations of:
- Plain text
- HTML
- Rich-text clipboard data
- Markdown-like formatting
- Platform-specific formatting syntax
This is why pasting the same content into different applications can produce different results.
The destination application decides which formatting representations it understands.
That's a useful lesson for developers working with document processing, editors, content management systems, or any application that moves data between platforms.
2. Unicode Can Make Text Look Bold Without HTML
Here's where things become interesting.
Consider two strings:
Hello World
ππ²πΉπΉπΌ πͺπΌπΏπΉπ±
They look like different styles of the same sentence.
But there's an important distinction.
The second line uses mathematical alphanumeric Unicode characters.
It isn't ordinary text with CSS applied.
These are different characters.
That difference allows certain styled characters to retain their appearance when copied into environments that don't preserve rich-text formatting.
A simple JavaScript transformation
Let's build a tiny example that converts uppercase Latin letters into their mathematical sans-serif bold equivalents.
function toUnicodeBold(text) {
const normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const styled = Array.from(
"πππππππππππππ π‘π’π£π€π₯π¦π§π¨π©πͺπ«π¬π"
);
return Array.from(text)
.map(char => {
const index = normal.indexOf(char);
return index === -1
? char
: styled[index];
})
.join("");
}
console.log(toUnicodeBold("HELLO DEV"));
Output:
πππππ’ πππ©
This is a simplified example supporting only AβZ.
A real-world formatter needs to consider lowercase letters, numbers, multiple styles, unsupported characters, and consistent transformations.
However, the core operation is predictable:
Input β Character mapping β Output
No AI model is required.
No external API is required.
For this specific transformation, plain JavaScript is sufficient.
That leads to a broader architectural principle:
Don't introduce nondeterministic systems into problems that have deterministic solutions.
3. But Unicode Formatting Isn't the Same as True Bold Text
There is an important downside to the approach above.
Unicode-styled characters can create accessibility challenges.
For example, a screen reader may announce mathematical alphanumeric characters differently from ordinary Latin letters.
Unicode transformations may also affect:
- Search and indexing
- Copying and editing
- Character limits
- Text normalization
- Compatibility with some fonts and applications
For accessibility and semantics, real text formatting is generally preferable when a platform supports it.
Unicode-styled text is best understood as a visual workaround, not a universal replacement for rich-text formatting.
This distinction matters because engineering decisions should consider more than what appears correct on a screen.
A feature that works visually may still create problems for someone using assistive technology.
4. Different Platforms Require Different Formatting Strategies
Let's compare two common publishing scenarios.
WhatsApp supports special formatting syntax, including:
*Hello* β Bold
_Hello_ β Italic
~Hello~ β Strikethrough
This is a syntax-based approach.
We can write a simple function:
function formatForWhatsApp(text, style) {
const markers = {
bold: "*",
italic: "_",
strike: "~"
};
const marker = markers[style];
if (!marker) {
return text;
}
return `${marker}${text}${marker}`;
}
console.log(
formatForWhatsApp("Hello Developers", "bold")
);
Output:
*Hello Developers*
The destination application interprets these markers.
LinkedIn's regular post composer does not interpret WhatsApp-style markers as bold formatting.
This is one reason Unicode-based formatting tools exist.
For example, the LinkedIn Text Formatter on Sarticles provides a browser-based way to prepare styled text for LinkedIn.
The engineering takeaway isn't about any particular formatter.
It's about understanding that two applications can require completely different transformations for the same user intention.
The user wants bold text.
But the implementation depends on the target platform.
This pattern appears everywhere in software engineering:
- Exporting files between systems
- Mapping data types between databases
- Converting API payloads
- Migrating legacy applications
- Processing documents across formats
We should design transformations around the requirements of the receiving system, not just the source.
5. JavaScript Character Counting Has a Hidden Problem
Now let's examine something even simpler.
How many characters are in a string?
Most developers start with:
const text = "Hello World";
console.log(text.length);
Output:
11
Easy.
But try this:
console.log("π".length);
Output:
2
Why does JavaScript report two?
Because JavaScript strings use UTF-16, and .length counts UTF-16 code units.
Some Unicode characters require two code units.
Counting Unicode code points
We can instead use:
const emoji = "π";
console.log(Array.from(emoji).length);
Output:
1
Much better.
But we're not finished.
Consider a family emoji:
const family = "π¨βπ©βπ§βπ¦";
console.log(family.length);
console.log(Array.from(family).length);
Output:
11
7
Yet a person typically perceives this as one emoji.
So what should a character counter display?
The answer depends on what we're measuring.
Introducing Intl.Segmenter
JavaScript provides Intl.Segmenter, which can divide text into grapheme clusters.
These often correspond more closely to user-perceived characters.
const segmenter = new Intl.Segmenter("en", {
granularity: "grapheme"
});
function countGraphemes(text) {
return [...segmenter.segment(text)].length;
}
console.log(countGraphemes("π"));
// 1
console.log(countGraphemes("π¨βπ©βπ§βπ¦"));
// 1
This is a significantly better starting point when we want to count user-perceived characters.
However, platform-specific limits may follow their own rules.
For example, the number a social platform counts might not always match the number of grapheme clusters.
That means developers need to choose the correct counting method for their use case.
A tool such as the LinkedIn Character Counter addresses the practical need to measure text when preparing LinkedIn content.
The larger lesson is that a feature described as βjust count charactersβ can contain more complexity than its interface suggests.
6. The Clipboard API Removes an Unnecessary Step
A text formatting tool is only useful if users can easily move the result into another application.
Imagine a workflow where users have to:
- Select the output manually.
- Copy it.
- Switch to another tab.
- Paste it into the destination.
We can simplify part of that experience with the Clipboard API.
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
return {
success: true,
message: "Copied successfully"
};
} catch (error) {
return {
success: false,
message: "Unable to copy text"
};
}
}
Usage:
copyButton.addEventListener("click", async () => {
const result = await copyToClipboard(
outputElement.value
);
statusElement.textContent = result.message;
});
The Clipboard API generally requires a secure context, and browser behavior can depend on user interaction and permissions.
A production implementation should also account for unsupported environments and provide a fallback when necessary.
But the important idea is straightforward:
Small UX improvements can have an outsized impact on usability.
A single Copy button can eliminate several repetitive interactions.
7. Not Everything Needs a Backend
Suppose we're building a text formatter.
The core requirements are:
- Accept text input
- Apply a predictable transformation
- Display the result
- Copy the transformed text
- Work on desktop and mobile browsers
Do we need a database?
Probably not.
Do we need authentication?
Not necessarily.
Do we need a server to process every transformation?
Not if everything can run locally.
And do we need an AI model?
Not for deterministic character mapping.
A simple architecture might look like this:
User
|
v
HTML Text Input
|
v
JavaScript Processor
|
v
Unicode Transformation
|
v
Output Preview
|
v
Clipboard API
For this class of application, browser-side processing can provide several advantages:
- Low latency
- Reduced infrastructure requirements
- Fewer network dependencies
- No need to transmit text for the core transformation
- Simpler deployment
That doesn't automatically make every client-side application secure or private.
Analytics, third-party scripts, storage, and deployment configuration still matter.
But if a problem can be solved locally, it's worth asking whether a backend adds genuine value.
8. Where AI Actually Makes Sense
This doesn't mean AI has no role in writing tools.
There are plenty of cases where it adds value.
For example:
AI is useful for:
- Generating a first draft
- Rewriting unclear sentences
- Suggesting alternative headlines
- Summarizing long content
- Changing tone or structure
- Translating between languages
Deterministic JavaScript is useful for:
- Counting characters
- Converting text to supported Unicode styles
- Inserting formatting markers
- Removing extra whitespace
- Copying output
- Validating input length
These capabilities can complement each other.
An AI system might help produce content.
A deterministic utility can then format, validate, and prepare that content for publishing.
Separating these responsibilities often makes the architecture easier to reason about.
It also helps developers choose where API usage, latency, and operational cost are justified.
9. Small Tools Can Teach Big Engineering Lessons
One of the most useful ideas in software development is that complexity should follow requirements.
Not trends.
Consider everything involved in a basic text utility:
Data representation: How is formatting encoded?
Interoperability: Will the output work in another application?
Unicode: Are we counting code units, code points, or grapheme clusters?
Accessibility: Can assistive technology interpret the output?
Browser compatibility: Do all targeted browsers support the APIs?
User experience: How many steps does it take to complete the task?
Architecture: Is server-side processing necessary?
These are real software engineering questions.
And they appear inside applications that might contain only a few hundred lines of code.
That's one reason small development projects are valuable.
They make it easier to focus on fundamentals without getting distracted by large infrastructure.
10. Final Thoughts
We're living through an exciting period in software development.
AI-assisted coding is making it easier to prototype ideas, explore architectures, and experiment with applications.
But the fundamentals haven't disappeared.
Developers still need to understand data types, encoding, interfaces, accessibility, and system boundaries.
Sometimes the correct solution involves machine learning.
Sometimes it requires a backend with sophisticated infrastructure.
And sometimes it requires a relatively small JavaScript function.
The challenge is knowing the difference.
Good software isn't defined by how much technology it uses. It's defined by how effectively it solves a problem.
And sometimes, building a simple text formatter teaches us more about engineering than building another complicated application.
Further reading
Disclosure: I have an ownership interest in Sarticles, which is referenced as an example of browser-based text utilities. The JavaScript snippets are illustrative educational examples, not extracts from its production source code.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.