CSS Pseudo-Elements: The Complete Guide with Examples

February 12, 2026

Pseudo-elements let you style specific parts of an element or insert generated content without adding extra HTML markup. They are one of the most powerful and underused features in CSS. With pseudo-elements you can add decorative icons, create tooltips, build custom list markers, style text selections, implement ribbon effects, and handle layout edge cases — all without touching your HTML.

This guide covers every standard CSS pseudo-element in practical depth: ::before and ::after, ::first-line and ::first-letter, ::marker, ::selection, ::placeholder, and ::backdrop. Each section includes working code examples you can copy directly into your projects.

⚙ Quick reference: For a printable overview of all CSS selectors including pseudo-elements, see our CSS Selectors Cheat Sheet. Use the CSS Beautifier to format your stylesheets for easier reading.

Table of Contents

  1. What Are Pseudo-Elements?
  2. Double Colon vs Single Colon Syntax
  3. The Content Property Deep Dive
  4. Decorative Effects with ::before and ::after
  5. ::first-line and ::first-letter
  6. Custom List Markers with ::marker
  7. Text Selection Styling with ::selection
  8. Form Placeholder Styling with ::placeholder
  9. Dialog and Fullscreen with ::backdrop
  10. CSS Counters with Pseudo-Elements
  11. Tooltip Implementation
  12. Ribbon and Badge Effects
  13. Clearfix and Layout Patterns
  14. Accessibility Considerations
  15. Real-World Patterns
  16. Performance Tips

What Are Pseudo-Elements?

A pseudo-element targets a specific part of an element rather than the element itself. Unlike pseudo-classes (which select elements in a certain state like :hover), pseudo-elements create virtual sub-elements that you can style independently. They do not exist in the DOM — you will not find them in the HTML source — but the browser renders them as if they were real elements.

Double Colon vs Single Colon Syntax

CSS3 introduced the double colon (::) notation to distinguish pseudo-elements from pseudo-classes. For backward compatibility, browsers still accept the single colon for the four original pseudo-elements (:before, :after, :first-line, :first-letter). Newer pseudo-elements like ::marker, ::selection, ::placeholder, and ::backdrop only support the double colon syntax.

/* Modern syntax (recommended for all) */
p::before      { content: "..."; }
p::after       { content: "..."; }
p::first-line  { font-weight: bold; }
p::first-letter { font-size: 2em; }

/* Legacy syntax (still works for the above four only) */
p:before       { content: "..."; }
p:after        { content: "..."; }

/* These REQUIRE double colon — no legacy fallback */
li::marker        { color: blue; }
::selection       { background: red; }
input::placeholder { color: gray; }
dialog::backdrop   { background: rgba(0,0,0,0.5); }

Best practice: always use the double colon syntax. It is clearer, future-proof, and explicitly communicates that you are targeting a pseudo-element rather than a pseudo-class.

The Content Property Deep Dive

The content property is what makes ::before and ::after work. Without it, these pseudo-elements do not render at all. The property accepts several value types beyond simple strings.

/* String values */
.note::before { content: "Note: "; font-weight: 700; color: #f59e0b; }
.checkmark::before { content: "\2713"; }    /* Unicode check mark */
.arrow::after { content: "\2192"; }         /* Right arrow */
.warning::before { content: "\26A0 "; }     /* Warning triangle */

/* Empty string — for purely decorative pseudo-elements */
.divider::after {
    content: "";
    display: block;
    height: 2px;
    background: linear-gradient(to right, #3b82f6, transparent);
}

/* attr() — pull content from an HTML attribute */
a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.8em;
    color: #9ca3af;
}

/* Display data attribute values as badges */
.badge::after {
    content: attr(data-count);
    background: #ef4444;
    color: white;
    border-radius: 50%;
    padding: 0.1rem 0.4rem;
    font-size: 0.75rem;
    margin-left: 0.5rem;
}
<!-- attr() example -->
<a href="https://example.com">Visit Example</a>
<!-- Renders: Visit Example (https://example.com) -->

<span class="badge" data-count="5">Notifications</span>
<!-- Renders: Notifications [red 5] -->
/* Image values */
.pdf-link::before {
    content: url('/icons/pdf.svg');
    width: 1rem; height: 1rem;
    margin-right: 0.5rem;
    vertical-align: middle;
}

/* For more sizing control, use empty content + background */
.external-link::after {
    content: "";
    display: inline-block;
    width: 0.75rem; height: 0.75rem;
    margin-left: 0.25rem;
    background: url('/icons/external.svg') no-repeat center / contain;
}

/* Combining multiple values */
h2::before { content: "Section " counter(section) ". "; color: #3b82f6; }

/* Open and close quotes */
blockquote::before { content: open-quote; font-size: 2rem; color: #3b82f6; }
blockquote::after  { content: close-quote; font-size: 2rem; color: #3b82f6; }

Decorative Effects with ::before and ::after

::before and ::after create child elements as the first and last children of the target element. Since they are generated by CSS, they keep your HTML clean of decorative markup.

Animated Underline

.fancy-link {
    position: relative;
    text-decoration: none;
    color: #3b82f6;
}
.fancy-link::after {
    content: "";
    position: absolute;
    bottom: -2px; left: 0;
    width: 0; height: 2px;
    background: #3b82f6;
    transition: width 0.3s ease;
}
.fancy-link:hover::after { width: 100%; }

Background Overlay on Hero Images

.hero {
    position: relative;
    background: url('hero.jpg') center / cover;
    padding: 4rem 2rem; color: white;
}
.hero::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 0;
}
.hero > * { position: relative; z-index: 1; }

Accent Bars and Corner Triangles

/* Left gradient accent bar */
.accent-card {
    position: relative;
    padding: 1.5rem 1.5rem 1.5rem 2rem;
    background: #1a1d27; border-radius: 8px;
}
.accent-card::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 4px;
    background: linear-gradient(to bottom, #3b82f6, #8b5cf6);
    border-radius: 8px 0 0 8px;
}

/* Corner triangle badge */
.featured-card { position: relative; overflow: hidden; }
.featured-card::after {
    content: "";
    position: absolute; top: 0; right: 0;
    border-style: solid;
    border-width: 0 3rem 3rem 0;
    border-color: transparent #3b82f6 transparent transparent;
}

Required Field Asterisks and Quotation Marks

/* Red asterisk on required labels */
label.required::after { content: " *"; color: #ef4444; font-weight: 700; }

/* Large decorative quotes */
blockquote.styled { position: relative; padding: 2rem 3rem; font-style: italic; }
blockquote.styled::before {
    content: "\201C";
    position: absolute; top: -0.5rem; left: 0.5rem;
    font-size: 4rem; color: #3b82f6; opacity: 0.4;
    font-style: normal; line-height: 1;
}

::first-line and ::first-letter

These pseudo-elements target typographic parts of block-level elements, ideal for editorial-style layouts.

/* Classic drop cap */
.article > p:first-of-type::first-letter {
    float: left;
    font-size: 3.5rem;
    line-height: 0.8;
    font-weight: 700;
    color: #3b82f6;
    margin-right: 0.5rem;
    margin-top: 0.15rem;
}

/* Boxed drop cap variant */
.boxed-dropcap > p:first-of-type::first-letter {
    float: left;
    font-size: 2.5rem; line-height: 1; font-weight: 700;
    color: white; background: #3b82f6;
    padding: 0.25rem 0.5rem; margin-right: 0.75rem;
    border-radius: 4px;
}

/* Bold first line */
.article p:first-of-type::first-line {
    font-weight: 700;
    color: #e4e4e7;
}

/* Small caps first line */
.intro::first-line {
    font-variant: small-caps;
    letter-spacing: 0.05em;
}

::first-line is dynamic: the browser recalculates which text falls on the first line when the element resizes. Only limited CSS properties apply to both: font properties, color, background, text-decoration, text-transform, letter-spacing, and word-spacing. ::first-letter only works on block-level elements and includes any preceding punctuation (like an opening quotation mark).

Custom List Markers with ::marker

The ::marker pseudo-element targets list item bullets and numbers directly, replacing the old list-style: none plus ::before workaround.

/* Color and size bullets */
ul li::marker { color: #3b82f6; font-size: 1.2em; }

/* Style ordered list numbers */
ol li::marker { color: #f59e0b; font-weight: 700; }

/* Custom marker content */
ul.checklist li::marker { content: "\2713 "; color: #22c55e; }
ul.arrow-list li::marker { content: "\25B6 "; color: #3b82f6; font-size: 0.8em; }

/* Summary/details marker */
summary::marker { color: #3b82f6; font-size: 1.2em; }
<!-- HTML -->
<ul class="checklist">
    <li>Set up project</li>
    <li>Write tests</li>
    <li>Deploy to production</li>
</ul>

::marker supports limited properties: color, content, font properties, white-space, and animation properties. You cannot set background, padding, or border on it. For full control, the ::before technique is still necessary.

Text Selection Styling with ::selection

/* Global selection color */
::selection { background: #3b82f6; color: white; }

/* Different selection for code blocks */
pre::selection, pre *::selection, code::selection {
    background: #1e3a5f; color: #e4e4e7;
}

/* Inverted selection for dark backgrounds */
.dark-section ::selection { background: #f59e0b; color: #0f1117; }

Only a few properties work with ::selection: color, background-color, text-decoration, text-shadow, and -webkit-text-stroke. It does not inherit — use a descendant selector like .parent ::selection to cascade. Always set both background and color to ensure adequate contrast.

Form Placeholder Styling with ::placeholder

/* Style all placeholders */
input::placeholder, textarea::placeholder {
    color: #6b7280;
    font-style: italic;
    opacity: 1;   /* Firefox sets default opacity below 1 */
}

/* Placeholder fades on focus */
input:focus::placeholder {
    opacity: 0.4;
    transition: opacity 0.3s ease;
}

/* Error state placeholder */
input:invalid::placeholder { color: #ef4444; opacity: 0.7; }

Firefox historically renders placeholder text with reduced opacity. Add opacity: 1 to fix this. Remember that placeholders are not labels — they should provide supplementary hints, not replace proper <label> elements.

Dialog and Fullscreen with ::backdrop

::backdrop styles the backdrop behind modal <dialog> elements and fullscreen elements. It covers the entire viewport and sits between the modal and the rest of the page.

/* Blurred dark backdrop */
dialog::backdrop {
    background: rgba(15, 17, 23, 0.8);
    backdrop-filter: blur(4px);
}

/* Fullscreen video backdrop */
video:fullscreen::backdrop { background: black; }

/* Danger dialog variant */
dialog.danger::backdrop {
    background: rgba(239, 68, 68, 0.2);
    backdrop-filter: blur(4px);
}
<!-- HTML -->
<dialog id="modal">
    <h2>Modal Title</h2>
    <p>This dialog has a blurred, dark backdrop.</p>
    <button onclick="this.closest('dialog').close()">Close</button>
</dialog>
<button onclick="document.getElementById('modal').showModal()">Open</button>

::backdrop does not inherit from any element. It exists in its own stacking context, so you must explicitly set all styles you want — nothing cascades into it.

CSS Counters with Pseudo-Elements

CSS counters create automatically incrementing numbers entirely in CSS. Combined with ::before, they are perfect for numbered sections and step indicators.

Basic Counter

.steps { counter-reset: step-counter; }

.steps .step {
    counter-increment: step-counter;
    padding-left: 3rem;
    position: relative;
    margin-bottom: 1.5rem;
}

.steps .step::before {
    content: counter(step-counter);
    position: absolute; left: 0; top: 0;
    width: 2rem; height: 2rem;
    background: #3b82f6; color: white;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 0.9rem;
}
<!-- HTML: each step gets a numbered blue circle -->
<div class="steps">
    <div class="step">Create your project</div>
    <div class="step">Install dependencies</div>
    <div class="step">Write your code</div>
    <div class="step">Deploy to production</div>
</div>

Nested Counters and Styles

/* Auto-numbering headings: 1. Section / 1.1 Subsection */
body { counter-reset: section; }
h2 { counter-reset: subsection; counter-increment: section; }
h2::before { content: counter(section) ". "; color: #3b82f6; }
h3 { counter-increment: subsection; }
h3::before { content: counter(section) "." counter(subsection) " "; color: #9ca3af; }

/* Roman numeral list */
.roman-list { counter-reset: roman; }
.roman-list li { counter-increment: roman; list-style: none; }
.roman-list li::before {
    content: counter(roman, upper-roman) ". ";
    color: #f59e0b; font-weight: 600;
}

/* Alphabetic list */
.alpha-list { counter-reset: alpha; }
.alpha-list li { counter-increment: alpha; list-style: none; }
.alpha-list li::before {
    content: counter(alpha, upper-alpha) ") ";
    color: #3b82f6; font-weight: 600;
}

Tooltip Implementation

CSS-only tooltips use a data attribute for the text and pseudo-elements for rendering.

[data-tooltip] { position: relative; cursor: help; }

/* Tooltip text bubble */
[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px); left: 50%;
    transform: translateX(-50%);
    background: #1a1d27; color: #e4e4e7;
    padding: 0.5rem 0.75rem; border-radius: 6px;
    font-size: 0.85rem; white-space: nowrap;
    z-index: 100; pointer-events: none;
    opacity: 0; transition: opacity 0.2s ease;
    border: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

/* Tooltip arrow */
[data-tooltip]::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 2px); left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: #1a1d27;
    z-index: 101; pointer-events: none;
    opacity: 0; transition: opacity 0.2s ease;
}

/* Show on hover */
[data-tooltip]:hover::after,
[data-tooltip]:hover::before { opacity: 1; }
<!-- HTML -->
<button data-tooltip="Save your changes">Save</button>

CSS-only tooltips have limitations: they cannot reposition to stay in the viewport, they do not work on touch devices, and they are not reliably announced by screen readers. For production tooltips that need accessibility, use JavaScript and ARIA attributes.

Ribbon and Badge Effects

/* Corner ribbon on a card */
.card-ribbon { position: relative; overflow: hidden; }
.card-ribbon::before {
    content: "NEW";
    position: absolute;
    top: 1.5rem; right: -2.5rem;
    width: 8rem; text-align: center;
    padding: 0.35rem 0;
    background: #3b82f6; color: white;
    font-size: 0.75rem; font-weight: 700;
    letter-spacing: 0.05em;
    transform: rotate(45deg);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

/* Status dot indicators */
.status { display: inline-flex; align-items: center; gap: 0.5rem; }
.status::before {
    content: "";
    width: 0.5rem; height: 0.5rem;
    border-radius: 50%; flex-shrink: 0;
}
.status.online::before {
    background: #22c55e;
    box-shadow: 0 0 6px rgba(34, 197, 94, 0.4);
}
.status.offline::before { background: #6b7280; }
.status.busy::before {
    background: #ef4444;
    box-shadow: 0 0 6px rgba(239, 68, 68, 0.4);
}
<!-- HTML -->
<span class="status online">Available</span>
<span class="status busy">Do not disturb</span>
<span class="status offline">Offline</span>

Clearfix and Layout Patterns

Before Flexbox and Grid, the clearfix hack was essential for containing floated children. While modern layout methods have made it largely unnecessary, pseudo-elements remain useful for other layout patterns.

/* Classic clearfix: forces container to wrap floated children */
.clearfix::after {
    content: "";
    display: table;
    clear: both;
}

/* Modern alternative — no pseudo-element needed */
.container { display: flow-root; }

/* Separator lines between items */
.breadcrumb-item + .breadcrumb-item::before {
    content: "/";
    color: #6b7280;
    margin: 0 0.5rem;
}

/* Dot separator */
.tag + .tag::before {
    content: "\00B7";  /* Middle dot */
    margin: 0 0.5rem;
    color: #6b7280;
}

/* Horizontal rule between sections */
.section + .section::before {
    content: "";
    display: block;
    width: 4rem; height: 2px;
    background: #3b82f6;
    margin: 2rem auto;
}

Accessibility Considerations

Screen readers handle ::before and ::after content inconsistently — some announce it, some skip it. Follow these rules:

1. Never put essential information in pseudo-elements.

/* BAD — screen readers may miss this */
.price::after { content: " per month"; }
/* A screen reader might say "$9.99" instead of "$9.99 per month" */

/* GOOD — essential text in the HTML */
<span class="price">$9.99 per month</span>

2. Decorative content is fine as pseudo-elements.

/* OK — the text "Important information" conveys meaning */
.info-icon::before { content: "\2139"; margin-right: 0.5rem; color: #3b82f6; }

/* BAD — icon is the only content with no accessible label */
<span class="info-icon"></span>

/* FIXED — add aria-label */
<span class="info-icon" aria-label="Information"></span>

3. CSS-only tooltips are not accessible. For tooltips containing important information, use HTML with role="tooltip" and aria-describedby.

4. The alt text syntax for content images (content: url('icon.svg') / "description") is in the CSS spec but has limited browser support. Use aria-label on the element instead.

The general rule: if removing all ::before and ::after content would cause a user to miss something important, that content belongs in the HTML.

Real-World Patterns

External Link Indicator

a[href^="http"]:not([href*="devtoolbox.dedyn.io"])::after {
    content: " \2197";
    font-size: 0.75em;
    vertical-align: super;
    opacity: 0.7;
}

Print Styles: Show URLs

@media print {
    a[href^="http"]::after {
        content: " (" attr(href) ")";
        font-size: 0.8em; color: #666;
    }
    abbr[title]::after {
        content: " (" attr(title) ")";
        font-size: 0.8em;
    }
}

Custom Checkbox

.custom-checkbox input[type="checkbox"] {
    position: absolute; opacity: 0; width: 0; height: 0;
}
.custom-checkbox label {
    position: relative; padding-left: 2rem;
    cursor: pointer; display: inline-block;
}
/* Checkbox box */
.custom-checkbox label::before {
    content: "";
    position: absolute; left: 0; top: 0.1rem;
    width: 1.25rem; height: 1.25rem;
    border: 2px solid #2a2e3a; border-radius: 4px;
    background: #0f1117; transition: all 0.2s ease;
}
/* Check mark (hidden by default) */
.custom-checkbox label::after {
    content: "";
    position: absolute; left: 0.4rem; top: 0.25rem;
    width: 0.45rem; height: 0.75rem;
    border: solid #3b82f6; border-width: 0 2.5px 2.5px 0;
    transform: rotate(45deg);
    opacity: 0; transition: opacity 0.2s ease;
}
/* Checked state */
.custom-checkbox input:checked + label::before {
    border-color: #3b82f6; background: rgba(59, 130, 246, 0.1);
}
.custom-checkbox input:checked + label::after { opacity: 1; }
.custom-checkbox input:focus-visible + label::before {
    outline: 2px solid #3b82f6; outline-offset: 2px;
}
<!-- HTML -->
<div class="custom-checkbox">
    <input type="checkbox" id="agree">
    <label for="agree">I agree to the terms</label>
</div>

Loading Spinner

/* Pure CSS spinner */
.spinner { position: relative; width: 2rem; height: 2rem; }
.spinner::after {
    content: "";
    position: absolute; inset: 0;
    border: 3px solid rgba(59, 130, 246, 0.2);
    border-top-color: #3b82f6;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Button loading state */
.btn.loading {
    color: transparent; pointer-events: none; position: relative;
}
.btn.loading::after {
    content: "";
    position: absolute; top: 50%; left: 50%;
    width: 1.2rem; height: 1.2rem;
    margin: -0.6rem 0 0 -0.6rem;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top-color: white; border-radius: 50%;
    animation: spin 0.6s linear infinite;
}

Text Truncation Fade

.truncate-fade {
    position: relative;
    max-height: 4.5rem; overflow: hidden;
}
.truncate-fade::after {
    content: "";
    position: absolute; bottom: 0; left: 0; right: 0;
    height: 2rem;
    background: linear-gradient(transparent, #0f1117);
    pointer-events: none;
}

Performance Tips

Pseudo-elements are real elements in the render tree. They participate in layout, painting, and compositing. Here are the key considerations:

/* 1. Every pseudo-element adds to the render tree.
   On a page with 100 .cards, ::before and ::after = 200 extra elements.
   Rarely a problem, but be aware on pages with thousands of elements. */

/* 2. Prefer transform/opacity for animations (compositor thread) */
.element::after {
    content: "";
    transform: scale(0); opacity: 0;
    transition: transform 0.3s, opacity 0.3s;
}
.element:hover::after { transform: scale(1); opacity: 1; }

/* AVOID animating width/height — triggers reflow */
.element::after { width: 0; transition: width 0.3s; }  /* Bad */

/* 3. Use content: none to fully remove unused pseudo-elements */
@media (max-width: 768px) {
    .decorative::before, .decorative::after {
        content: none;  /* Better than display: none — not created at all */
    }
}

/* 4. Limit repaint areas with contain */
.overlay-container {
    position: relative;
    contain: paint;  /* Repaints limited to this element */
}

/* 5. Use will-change sparingly for animated pseudo-elements */
.card::before {
    will-change: transform;  /* Promotes to own compositing layer */
}

In practice, pseudo-element performance is almost never a bottleneck. The biggest CSS performance concerns are forced synchronous layouts from JavaScript and expensive properties like box-shadow with large blur radii. Write clear, maintainable pseudo-element styles first, and optimize only if profiling shows a genuine issue.

📚 DevToolbox resources on CSS:
CSS Selectors: The Complete Guide — Every selector type including pseudo-classes
CSS Grid: The Complete Guide — Master two-dimensional layout
CSS Animations: The Complete Guide — Transitions, keyframes, and performance
CSS Selectors Cheat Sheet — Quick-reference for every selector type
CSS Clip Path Generator — Create clipping shapes visually
CSS Beautifier — Format and prettify CSS code

Frequently Asked Questions

What is the difference between ::before and ::after pseudo-elements?

::before inserts generated content as the first child of the selected element, while ::after inserts it as the last child. Both require the content property to render. The choice depends on whether you want generated content to appear before or after the element's existing content. In practice, both are used interchangeably for decorative effects when absolute positioning is involved, since position overrides the natural flow order.

Why do my ::before and ::after pseudo-elements not show up?

The most common reason is a missing content property. Without it, the pseudo-element does not render — even content: "" (an empty string) is required. Other causes include targeting a replaced element (<img>, <input>, <br>) which cannot have pseudo-element children, or the element having display: none. Also check that content: none is not set elsewhere in your CSS.

Can I use ::before and ::after on img, input, or br elements?

No. ::before and ::after do not work on replaced elements or void elements. Replaced elements like <img>, <video>, and <iframe> have content from an external resource. Void elements like <input>, <br>, and <hr> cannot have children. To add decorative content near these elements, wrap them in a <span> or <div> and apply the pseudo-element to the wrapper.

Are pseudo-element contents accessible to screen readers?

Screen reader behavior is inconsistent. Some announce ::before and ::after content, others ignore it. The safe rule: never put essential information in pseudo-elements. Use them for decorative content only. If content must be conveyed to assistive technology, put it in the HTML and use ARIA attributes as needed.

Should I use single colon (:before) or double colon (::before)?

Always use double colon. It was introduced in CSS3 to distinguish pseudo-elements from pseudo-classes. While browsers still accept single colon for the four originals, newer pseudo-elements like ::marker, ::selection, ::placeholder, and ::backdrop only support double colon. Using it consistently makes your code clearer and future-proof.

How do CSS counters work with pseudo-elements?

CSS counters are variables maintained by CSS whose values can be incremented. Initialize with counter-reset on a parent, increment with counter-increment on children, and display using counter() inside content on ::before or ::after. Counters support nesting (for section numbering like 1.1, 1.2) and styles like decimal, roman numerals, and alphabetic. They add automatic numbering without JavaScript.

Related Resources

CSS Selectors: The Complete Guide
Every selector type with practical examples
CSS Grid: The Complete Guide
Master two-dimensional CSS layout
CSS Clip Path Generator
Create complex clipping shapes visually
CSS Selectors Cheat Sheet
Quick reference for every CSS selector type
CSS Animations: The Complete Guide
Transitions, keyframes, and animation performance
CSS Beautifier
Format and prettify CSS code