CSS3 & Responsive Design

Text Styling & Decoration

25 min Lesson 11 of 60

Introduction to Text Styling

While font properties control the typeface, size, and weight of characters, CSS text properties control how those characters are arranged, spaced, decorated, and displayed within their containers. Text styling is the bridge between choosing a good font and creating truly professional, readable typography. In this lesson, we will explore every major CSS text property in depth -- from alignment and decoration to spacing, wrapping, shadows, and beyond.

Mastering text styling allows you to create elegant headings, readable body text, truncated previews, creative effects, and layouts that work across different languages and writing systems. These properties are among the most frequently used in everyday CSS, and understanding their nuances will significantly improve your web designs.

Text Alignment with text-align

The text-align property controls the horizontal alignment of inline content within a block-level element. It affects text, inline elements, and inline-block elements. This is one of the most fundamental layout properties in CSS.

Basic Alignment Values

  • left -- Aligns text to the left edge. This is the default for left-to-right (LTR) languages like English.
  • right -- Aligns text to the right edge. This is the default for right-to-left (RTL) languages like Arabic and Hebrew.
  • center -- Centers text horizontally within the element. Commonly used for headings, hero sections, and short content blocks.
  • justify -- Stretches the text so that each line has equal width, aligning both the left and right edges. The browser adjusts word spacing to achieve this. This creates a clean, newspaper-like appearance but can cause readability problems.

Example: Basic Text Alignment

.left-aligned {
    text-align: left;
}

.right-aligned {
    text-align: right;
}

.centered {
    text-align: center;
}

.justified {
    text-align: justify;
}

Logical Alignment Values: start and end

Modern CSS introduced start and end values that respect the document's writing direction. These are essential for building internationalized websites:

  • start -- Aligns to the start of the writing direction. In LTR languages, this means left. In RTL languages, this means right.
  • end -- Aligns to the end of the writing direction. In LTR languages, this means right. In RTL languages, this means left.

Example: Logical Alignment

/* These adapt automatically based on the dir attribute */
.content {
    text-align: start; /* Left in English, Right in Arabic */
}

.sidebar-note {
    text-align: end; /* Right in English, Left in Arabic */
}

/* HTML context */
<div dir="ltr">
    <p class="content">This aligns left</p>
</div>

<div dir="rtl">
    <p class="content">هذا يحاذي لليمين</p>
</div>
Tip: Always prefer text-align: start over text-align: left when building multilingual websites. This ensures your layout works correctly regardless of the language direction, and you avoid needing separate CSS overrides for RTL languages.

Justified Text Considerations

While justified text looks elegant in print media, it can cause problems on the web. The browser creates uneven gaps between words (called "rivers of whitespace") especially in narrow containers or with long words. This hurts readability, particularly for users with dyslexia or reading difficulties.

Example: Improving Justified Text

/* Basic justified text */
.article-body {
    text-align: justify;
}

/* Better: use hyphens to reduce gaps */
.article-body-improved {
    text-align: justify;
    hyphens: auto;
    -webkit-hyphens: auto;
    word-break: break-word;
}

/* Set the language for proper hyphenation */
/* In HTML: <p lang="en">...</p> */
Warning: Justified text should generally be avoided for narrow columns (under 40 characters per line). The word spacing becomes too irregular and creates a poor reading experience. Use left alignment (or start alignment) for narrow text containers.

Text Decoration

The text-decoration property adds visual decorations to text such as underlines, overlines, and strikethroughs. This property is most commonly associated with links but has many other uses in web design.

Basic Decoration Lines

  • underline -- Draws a line below the text. This is the default styling for links (<a> elements).
  • overline -- Draws a line above the text. Rarely used, but can be useful for certain design effects.
  • line-through -- Draws a line through the middle of the text (strikethrough). Commonly used to show deleted content, original prices on sale items, or completed tasks.
  • none -- Removes all decorations. Frequently used to remove the default underline from links.

Example: Basic Text Decoration

/* Remove link underlines */
a {
    text-decoration: none;
}

/* Add underline on hover */
a:hover {
    text-decoration: underline;
}

/* Strikethrough for sale prices */
.original-price {
    text-decoration: line-through;
    color: #999;
}

/* Overline effect */
.section-label {
    text-decoration: overline;
}

/* Multiple decorations */
.fancy-text {
    text-decoration: underline overline;
}

The text-decoration Shorthand

The text-decoration property is actually a shorthand for four individual properties. The full shorthand syntax is:

text-decoration: <line> <style> <color> <thickness>

  • text-decoration-line -- Which line(s) to draw: underline, overline, line-through, or combinations.
  • text-decoration-style -- The style of the line: solid, double, dotted, dashed, or wavy.
  • text-decoration-color -- The color of the decoration line. By default, it matches the text color.
  • text-decoration-thickness -- The thickness of the line. Accepts length values like 2px, 0.1em, or the keyword auto.

Example: text-decoration Shorthand Properties

/* Wavy red underline (like a spelling error) */
.spelling-error {
    text-decoration: underline wavy red;
}

/* Thick dotted underline */
.dotted-underline {
    text-decoration: underline dotted 2px;
}

/* Double overline in blue */
.double-overline {
    text-decoration: overline double blue;
}

/* Dashed line-through in grey */
.removed-text {
    text-decoration: line-through dashed #888;
}

/* Using individual properties for more control */
.custom-decoration {
    text-decoration-line: underline;
    text-decoration-style: wavy;
    text-decoration-color: #e74c3c;
    text-decoration-thickness: 3px;
}

/* Offset the underline from the text */
.offset-underline {
    text-decoration: underline;
    text-underline-offset: 4px; /* Space between text and underline */
}
Tip: The text-underline-offset property is incredibly useful for improving the look of underlined text. The default underline often cuts through descenders (letters like g, p, y). Adding a small offset of 2-4px separates the underline from the text and creates a much cleaner appearance.

Text Transform

The text-transform property changes the capitalization of text without modifying the actual HTML content. This is a powerful tool for maintaining consistent visual styling regardless of how the content is written in the source.

  • uppercase -- Converts all characters to uppercase. Common for buttons, labels, navigation items, and headings.
  • lowercase -- Converts all characters to lowercase.
  • capitalize -- Capitalizes the first letter of each word. Useful for titles and names.
  • none -- Removes any inherited text transformation. The text appears as written in the HTML.
  • full-width -- Converts characters to their full-width form. Mainly useful for East Asian typography where you need to align Latin characters with CJK characters.

Example: Text Transform

/* Uppercase navigation links */
nav a {
    text-transform: uppercase;
    letter-spacing: 0.05em; /* Add spacing for readability */
    font-size: 0.875rem;
}

/* Capitalize titles */
.article-title {
    text-transform: capitalize;
}

/* Lowercase email addresses */
.email {
    text-transform: lowercase;
}

/* Button text always uppercase */
.btn {
    text-transform: uppercase;
    font-weight: 600;
    letter-spacing: 0.1em;
}

/* Reset transform for child elements */
.uppercase-section {
    text-transform: uppercase;
}
.uppercase-section .normal-text {
    text-transform: none;
}
Note: When using text-transform: uppercase, always add a small amount of letter-spacing (0.05em to 0.1em). Uppercase text looks cramped without extra spacing because uppercase letters have more visual weight and straight edges that need breathing room.

Text Indentation with text-indent

The text-indent property sets the indentation of the first line of text in a block element. This is a classic typographic feature used in books and articles to mark the beginning of paragraphs.

Example: Text Indentation

/* Classic paragraph indentation */
.book-style p {
    text-indent: 2em;
    margin-bottom: 0; /* No space between paragraphs in book style */
}

/* First paragraph has no indent (book convention) */
.book-style p:first-of-type {
    text-indent: 0;
}

/* Hanging indent (negative value) */
.bibliography {
    text-indent: -2em;
    padding-left: 2em; /* Compensate with padding */
}

/* Percentage-based indent */
.wide-indent {
    text-indent: 10%; /* 10% of the container width */
}
Tip: A hanging indent (negative text-indent with matching padding-left) is the standard format for bibliography entries, legal citations, and glossary definitions. The first line extends to the left while subsequent lines are indented.

Letter Spacing and Word Spacing

These two properties give you fine control over the horizontal spacing of text. Used correctly, they can dramatically improve readability and visual appeal.

letter-spacing

The letter-spacing property adjusts the space between individual characters (also called tracking in typography). Positive values increase spacing; negative values decrease it.

Example: Letter Spacing

/* Tight letter spacing for large headings */
h1 {
    letter-spacing: -0.02em;
    font-size: 3rem;
}

/* Wide letter spacing for small uppercase text */
.label {
    text-transform: uppercase;
    letter-spacing: 0.15em;
    font-size: 0.75rem;
    font-weight: 600;
}

/* Normal letter spacing */
.body-text {
    letter-spacing: normal; /* Same as 0 */
}

/* Dramatic spacing for design effect */
.spaced-heading {
    letter-spacing: 0.5em;
    text-transform: uppercase;
}

word-spacing

The word-spacing property adjusts the space between words. It adds to (or subtracts from) the normal space character width.

Example: Word Spacing

/* Increase word spacing for readability */
.wide-words {
    word-spacing: 0.2em;
}

/* Decrease word spacing */
.tight-words {
    word-spacing: -0.05em;
}

/* Combine with letter spacing for headlines */
.hero-subtitle {
    word-spacing: 0.3em;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    font-size: 0.875rem;
}
Warning: Be careful with negative letter-spacing and word-spacing values. If you go too far, characters or words will overlap and become unreadable. Always test your spacing at different font sizes and on different devices.

Line Height

The line-height property controls the vertical space between lines of text (the leading). It is one of the most important properties for readability and is essential for professional typography.

Different Ways to Set Line Height

  • Unitless number (recommended) -- A multiplier of the element's font size. line-height: 1.5 means the line height is 1.5 times the font size. This is the recommended approach because it scales properly with font size changes and is inherited correctly by child elements.
  • Length value (px, em, rem) -- A fixed line height. line-height: 24px sets an exact line height regardless of font size. This can cause problems when font sizes change.
  • Percentage -- A percentage of the element's font size. line-height: 150% is similar to 1.5, but there is a critical difference in how it inherits: the computed value is inherited, not the percentage itself.
  • normal -- The browser's default line height, typically around 1.2. This is often too tight for body text.

Example: Line Height Values

/* Recommended: unitless line-height */
body {
    font-size: 16px;
    line-height: 1.6; /* = 25.6px, and children inherit the 1.6 multiplier */
}

/* Fixed line-height (be cautious) */
.fixed-leading {
    font-size: 16px;
    line-height: 24px; /* Exactly 24px regardless of font size */
}

/* Percentage line-height (inheritance issue) */
.parent {
    font-size: 16px;
    line-height: 150%; /* Computed: 24px. Children inherit 24px, NOT 150% */
}
.parent .child {
    font-size: 32px; /* Line height is still 24px! Text overlaps. */
}

/* Unitless avoids the inheritance problem */
.parent-better {
    font-size: 16px;
    line-height: 1.5; /* Children inherit 1.5 multiplier */
}
.parent-better .child {
    font-size: 32px; /* Line height = 32px * 1.5 = 48px. Correct! */
}
Note: The difference between unitless line-height and percentage line-height is a classic CSS gotcha. With percentages, the computed pixel value is inherited. With unitless values, the multiplier is inherited. Always use unitless values for body text to avoid overlapping text in nested elements with different font sizes.

Line Height Best Practices

Example: Line Height for Different Contexts

/* Body text: generous line height for readability */
body {
    line-height: 1.5; /* Minimum for body text */
}

/* Better for long-form reading */
article p {
    line-height: 1.6; /* to 1.8 for comfortable reading */
}

/* Headings: tighter line height */
h1, h2, h3 {
    line-height: 1.2; /* Headings look better with less spacing */
}

/* Large headings: even tighter */
.hero-title {
    font-size: 4rem;
    line-height: 1.1; /* Prevent excessive space in large text */
}

/* Single-line elements: match the height */
.button {
    line-height: 1; /* Text is vertically centered with padding */
    padding: 12px 24px;
}

/* Vertical centering trick with line-height */
.single-line-center {
    height: 50px;
    line-height: 50px; /* Match height for vertical centering */
}

White Space Handling

The white-space property controls how whitespace characters (spaces, tabs, newlines) inside an element are handled. This property is essential for displaying code, preserving formatting, and controlling text wrapping behavior.

White Space Values

  • normal -- The default. Collapses multiple whitespace characters into a single space. Lines wrap at the edge of the container.
  • nowrap -- Collapses whitespace like normal, but prevents line wrapping. Text continues on one line until a <br> tag is encountered. Useful for single-line elements that should not break.
  • pre -- Preserves all whitespace exactly as written in the HTML, including spaces, tabs, and newlines. Lines do not wrap. This behaves like the HTML <pre> element.
  • pre-wrap -- Preserves whitespace like pre, but allows lines to wrap when they reach the container edge. This is the best choice for displaying code that should wrap in narrow containers.
  • pre-line -- Collapses spaces and tabs into single spaces (like normal), but preserves newline characters. Lines also wrap at the container edge. Useful when you want line breaks from the source to be honored without preserving all spacing.
  • break-spaces -- Similar to pre-wrap, but any trailing whitespace at the end of a line is also preserved and does not hang. Additionally, those whitespace characters can be broken across lines.

Example: White Space Handling

/* Normal: collapses spaces, wraps text */
.normal {
    white-space: normal;
}

/* Nowrap: prevents line breaks */
.no-break {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis; /* Show ... when text is cut off */
}

/* Pre: preserves all formatting (for code blocks) */
.code-display {
    white-space: pre;
    font-family: monospace;
    background: #f4f4f4;
    padding: 1rem;
    overflow-x: auto; /* Allow horizontal scroll */
}

/* Pre-wrap: preserves formatting but wraps long lines */
.code-wrapping {
    white-space: pre-wrap;
    word-break: break-all; /* Break long strings */
}

/* Pre-line: honors newlines but collapses spaces */
.poetry {
    white-space: pre-line;
}

Reference: White Space Behavior Summary

/*
Value         | Spaces | Newlines | Wrapping
--------------+--------+----------+---------
normal        | Collapse| Collapse | Yes
nowrap        | Collapse| Collapse | No
pre           | Preserve| Preserve | No
pre-wrap      | Preserve| Preserve | Yes
pre-line      | Collapse| Preserve | Yes
break-spaces  | Preserve| Preserve | Yes
*/

Word Breaking and Overflow Wrapping

When text contains very long words, URLs, or continuous strings without spaces, it can overflow its container. CSS provides several properties to control how and when these long strings break.

word-break

The word-break property specifies how words should break when they reach the end of a line:

  • normal -- Words break at their normal break points (spaces, hyphens). Long words may overflow.
  • break-all -- Allows breaking within any word at any character. This is aggressive and should be used cautiously because it can break words in awkward places. Useful for CJK (Chinese, Japanese, Korean) text or data tables.
  • keep-all -- Prevents word breaks within CJK text. For non-CJK text, it behaves like normal.

overflow-wrap (formerly word-wrap)

The overflow-wrap property is less aggressive than word-break. It only breaks words if they would overflow their container:

  • normal -- Words are only broken at normal break points.
  • break-word -- If a word is too long to fit, it will break at an arbitrary point to prevent overflow. Unlike break-all, this only kicks in when necessary.
  • anywhere -- Like break-word, but the break is considered when calculating min-content sizing.

Example: Handling Long Words and URLs

/* The safest approach for most text */
.content {
    overflow-wrap: break-word;
}

/* For user-generated content with URLs */
.user-content {
    overflow-wrap: break-word;
    word-break: break-word; /* Legacy fallback */
}

/* Aggressive breaking for data tables */
.data-cell {
    word-break: break-all;
}

/* Handling long URLs in links */
a {
    overflow-wrap: break-word;
    word-break: break-all; /* Break URLs at any character */
}

/* Complete defensive text wrapping */
.safe-text {
    overflow-wrap: break-word;
    word-wrap: break-word; /* Old name, still works */
    hyphens: auto;
}
Tip: For most projects, adding overflow-wrap: break-word to your body or base content styles is a good defensive practice. It prevents layout-breaking overflow from unexpected long strings in user-generated content.

Text Overflow and Truncation

The text-overflow property specifies how overflowed content that is not displayed should be signaled to the user. The most common use is showing an ellipsis (...) when text is truncated.

The Classic Truncation Pattern

To create truncated text with an ellipsis, you need three properties working together. All three are required -- leaving out any one of them will break the effect:

Example: Single-Line Truncation

/* The truncation trio */
.truncate {
    white-space: nowrap;      /* Prevent text from wrapping */
    overflow: hidden;          /* Hide the overflowing text */
    text-overflow: ellipsis;   /* Show ... at the cut-off point */
}

/* Practical example: card title */
.card-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 250px;
}

/* Table cell truncation */
td {
    max-width: 200px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

Multi-Line Truncation

Truncating text after multiple lines requires a different approach using the -webkit-line-clamp property. Despite the webkit prefix, this is supported in all modern browsers:

Example: Multi-Line Truncation

/* Truncate after 2 lines */
.two-line-truncate {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Truncate after 3 lines */
.three-line-truncate {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Blog excerpt card */
.blog-excerpt {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.6;
    max-height: calc(1.6em * 4); /* Fallback for older browsers */
}

Text Shadow

The text-shadow property adds shadow effects to text. It can create depth, glow effects, outlines, and other creative visual treatments. The syntax is similar to box-shadow but without the spread value.

Text Shadow Syntax

text-shadow: offset-x offset-y blur-radius color;

  • offset-x -- Horizontal offset. Positive moves right, negative moves left.
  • offset-y -- Vertical offset. Positive moves down, negative moves up.
  • blur-radius -- Optional. How blurry the shadow is. Default is 0 (sharp shadow).
  • color -- Optional. The shadow color. Defaults to the text color (currentColor).

Example: Text Shadow Effects

/* Simple drop shadow */
.shadow-basic {
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}

/* No blur (hard shadow) */
.shadow-hard {
    text-shadow: 3px 3px 0 #333;
}

/* Glow effect */
.shadow-glow {
    color: #fff;
    text-shadow: 0 0 10px rgba(255, 255, 255, 0.8),
                 0 0 20px rgba(255, 255, 255, 0.6),
                 0 0 40px rgba(0, 150, 255, 0.4);
}

/* Embossed / letterpress effect */
.shadow-embossed {
    color: #ccc;
    text-shadow: 0 1px 0 #fff, 0 -1px 0 #333;
}

/* Retro 3D text */
.shadow-3d {
    color: #e74c3c;
    text-shadow: 1px 1px 0 #c0392b,
                 2px 2px 0 #a93226,
                 3px 3px 0 #922b21,
                 4px 4px 0 #7b241c;
}

/* Neon sign effect */
.shadow-neon {
    color: #fff;
    text-shadow: 0 0 7px #fff,
                 0 0 10px #fff,
                 0 0 21px #fff,
                 0 0 42px #0fa,
                 0 0 82px #0fa,
                 0 0 92px #0fa;
}

/* Outline text using multiple shadows */
.shadow-outline {
    color: #fff;
    text-shadow: -1px -1px 0 #000,
                  1px -1px 0 #000,
                 -1px  1px 0 #000,
                  1px  1px 0 #000;
}
Warning: Text shadows can impact rendering performance, especially when applied to large amounts of text or when using multiple shadow layers with large blur radii. Use complex text shadows sparingly -- typically only on headings or short decorative text, never on entire paragraphs of body text.

Writing Mode

The writing-mode property changes the direction in which text flows. By default, text flows horizontally from left to right. With writing-mode, you can create vertical text layouts used in East Asian languages or for creative design effects.

Example: Writing Modes

/* Default horizontal text */
.horizontal {
    writing-mode: horizontal-tb; /* Horizontal, top to bottom */
}

/* Vertical text, right to left (traditional Chinese/Japanese) */
.vertical-rl {
    writing-mode: vertical-rl;
}

/* Vertical text, left to right */
.vertical-lr {
    writing-mode: vertical-lr;
}

/* Vertical sidebar label */
.sidebar-label {
    writing-mode: vertical-rl;
    text-orientation: mixed;
    letter-spacing: 0.1em;
    font-size: 0.75rem;
    text-transform: uppercase;
}

/* Rotated text for design */
.rotated-label {
    writing-mode: vertical-lr;
    transform: rotate(180deg);
    /* This makes text read bottom-to-top */
}

Practical Typography Examples

Let us combine everything we have learned into real-world typography patterns that you will use frequently in your projects.

Example: Complete Typography System

/* Base typography */
body {
    font-family: system-ui, -apple-system, sans-serif;
    font-size: 16px;
    line-height: 1.6;
    color: #333;
    letter-spacing: normal;
    overflow-wrap: break-word;
}

/* Headings */
h1, h2, h3, h4, h5, h6 {
    line-height: 1.2;
    letter-spacing: -0.02em;
    text-wrap: balance;
}

h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.5rem; }

/* Paragraph spacing */
p + p {
    margin-top: 1em;
}

/* Links */
a {
    color: #2563eb;
    text-decoration: underline;
    text-decoration-color: rgba(37, 99, 235, 0.3);
    text-underline-offset: 2px;
    transition: text-decoration-color 0.2s;
}
a:hover {
    text-decoration-color: rgba(37, 99, 235, 1);
}

/* Small caps for abbreviations */
abbr {
    font-variant: small-caps;
    letter-spacing: 0.05em;
}

/* Blockquotes */
blockquote {
    text-indent: -0.4em; /* Hanging punctuation effect */
    font-style: italic;
    line-height: 1.8;
    color: #555;
}

/* Navigation */
nav a {
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.875rem;
    font-weight: 600;
}

/* Card description truncation */
.card-description {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.5;
}

/* Badge / label */
.badge {
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.625rem;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
}

Exercise 1: Styled Article Header

Create an article header with the following requirements:

  1. A category label above the title using uppercase text with wide letter spacing (0.15em), small font size, and a color of #6366f1.
  2. A main title (h1) with tight letter spacing (-0.03em), line-height of 1.15, and font-size of 2.5rem.
  3. A subtitle below the title with a lighter color (#666), line-height of 1.6, and font-size of 1.125rem.
  4. An author line with the author name underlined with a wavy decoration in the brand color.

Exercise 2: Text Truncation Card

Create a card component with these specifications:

  1. A card title that truncates to a single line with an ellipsis if it overflows.
  2. A card description that truncates after exactly 3 lines with an ellipsis.
  3. A "Read More" link with no default underline, but a custom underline appears on hover with a 2px offset and the color #3b82f6.
  4. A category badge using uppercase, wide letter spacing, and a nowrap white-space.

Exercise 3: Creative Text Effects

Create three different heading styles:

  1. A "neon glow" heading with white text, a dark background, and multiple text-shadow layers creating a glowing effect.
  2. A "3D" heading using stacked text shadows to create a three-dimensional depth effect.
  3. An "outlined" heading with white (or transparent) text and text-shadow creating an outline around each letter.

Exercise 4: Code Display Block

Style a code display block that:

  1. Preserves all whitespace and newlines (use pre-wrap).
  2. Uses a monospace font stack.
  3. Has a line-height of 1.7 for readability.
  4. Handles long lines by wrapping rather than horizontal scrolling.
  5. Uses word-break to prevent URLs or long strings from overflowing.

ES
Edrees Salih
19 hours ago

We are still cooking the magic in the way!