Menu

Tailwind CSS Content

When working with CSS, the content property plays a crucial role in defining content for pseudo-elements like ::before and ::after. Tailwind CSS provides content utility that can be used with the before and after modifiers to style and control the display of pseudo-elements.

ClassPropertiesExample
content-nonecontent: none;<div className="content-none"></div>

Overview of Content

Adding the Pseudo-elements Content

To add content to ::before or ::after pseudo-element, use the before:content-['*'] or after:content-['*'] variants. The text inside brackets defines the content to be displayed.

This is a live editor. Play around with it!
export default function StaticContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Adding static text with the after pseudo element */}
      <button className="after:content-['➡'] after:ml-2 px-4 py-2 bg-blue-500 text-white rounded-lg">
        Click Me
      </button>
    </div>
  );
}

Linking Attributes with Content

To allow pseudo-elements to reference an attribute value, use the content-[attr(attribute-name)] syntax to dynamically populate the pseudo-element:

This is a live editor. Play around with it!
export default function DynamicContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Adding static text with the after pseudo element */}
      <button after="➡" className="after:content-[attr(after)] after:ml-2 px-4 py-2 bg-blue-500 text-white rounded-lg">
        Click Me
      </button>
    </div>
  );
}

Adding Spaces and Underscores

To add a space inside the content, use underscores(_), e.g, content-["Spaced_Content"]. When you have to actually add an underscore, escape it with a backslash, e.g., content-["Underscored\_Content"].

This is a live editor. Play around with it!
export default function SpacedContent() {
  return (
    <div className="w-screen h-screen flex flex-col items-center justify-center">
      {/* Adding underscores and spaces in pseudo content */}
      <div className="before:content-['Spaced_Content'] text-lg before:mr-2 text-gray-600"></div>
      <div className="before:content-['Underscored\_Content'] text-lg before:mr-2 text-gray-600"></div>
    </div>
  );
}

States and Responsiveness

Hover and Focus States

Tailwind CSS allows you to conditionally apply content styles. By prepending state modifiers like hover: or focus:, you can change pseudo-element content based on user interaction:

This is a live editor. Play around with it!
export default function HoverStateContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Changing content dynamically on hover */}
      <button className="hover:after:content-['👉'] hover:after:ml-2 px-4 py-2 bg-purple-600 text-white rounded-lg">
        Explore
      </button>
    </div>
  );
}

Breakpoint Modifiers

Tailwind’s breakpoint modifiers allow you to modify the content of pseudo-elements based on screen size. This approach provides responsive designs by appending breakpoint prefixes such as sm:, md:, or lg:.

This is a live editor. Play around with it!
export default function ResponsiveContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Adjusting content for different breakpoints */}
      <div className="after:content-['Small'] md:after:content-['Medium'] lg:after:content-['Large']">
        Screen Size:
      </div>
    </div>
  );
}

Custom Content

Extending the Theme

Tailwind, by-default, only provides the content-none utility. However, it lets you define custom content values.

To add a new content utility, extend the content property in the theme section of your tailwind.config.js. Once added, use the custom values via their class names:

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

export default function CustomContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Using custom-defined pseudo-element content */}
      <span className="before:content-check text-green-500 mr-4">Success</span>
      <span className="before:content-cross text-red-500">Error</span>
    </div>
  );
}

Using Arbitrary Values

Arbitrary values allow you to use one-off values without extending the theme. To use an arbitrary content value, surround the desired value with square brackets [] in the utility class.

This is a live editor. Play around with it!
export default function ArbitraryContent() {
  return (
    <div className="w-screen h-screen flex items-center justify-center">
      {/* Inserting arbitrary emoji in the content */}
      <div className="before:content-['🌟'] text-xl text-yellow-500">
        Starred Item
      </div>
    </div>
  );
}

Real World Examples

Product Card with Sale Badge

A product card component that displays a "SALE" badge in the top-right corner using content-['SALE'] before pseudo-element.

This is a live editor. Play around with it!
const ProductCard = () => {
  const products = [
    {
      id: 1,
      name: "Leather Backpack",
      price: "$129.99",
      salePrice: "$89.99",
      src: "https://images.unsplash.com/photo-1548036328-c9fa89d128fa",
      alt: "Brown leather backpack"
    },
    {
      id: 2,
      name: "Wireless Headphones",
      price: "$199.99",
      salePrice: "$149.99",
      src: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e",
      alt: "Black wireless headphones"
    },
    // ... more products
  ];

  return (
    <div className="grid gap-6 p-8">
      {products.map(product => (
        <div key={product.id} className="relative group">
          <div className="before:absolute before:top-4 before:right-4 before:content-['SALE'] before:bg-red-500 before:text-white before:px-3 before:py-1 before:rounded-full before:text-sm">
            <img 
              src={product.src} 
              alt={product.alt}
              className="w-full h-64 object-cover rounded-lg"
            />
            <div className="p-4">
              <h3 className="text-lg font-semibold">{product.name}</h3>
              <div className="flex gap-2">
                <span className="line-through text-gray-500">{product.price}</span>
                <span className="text-red-500">{product.salePrice}</span>
              </div>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
};

export default ProductCard;

Required Form Fields Indicator

A form component that shows required fields with an asterisk using content-['*'] before pseudo-element.

This is a live editor. Play around with it!
const RegistrationForm = () => {
  const formFields = [
    { id: 1, name: "fullName", label: "Full Name", required: true },
    { id: 2, name: "email", label: "Email Address", required: true },
    { id: 3, name: "phone", label: "Phone Number", required: false },
    { id: 4, name: "company", label: "Company Name", required: true },
    { id: 5, name: "position", label: "Job Title", required: false },
    { id: 6, name: "department", label: "Department", required: true }
  ];

  return (
    <form className="max-w-2xl mx-auto p-8 space-y-6">
      {formFields.map(field => (
        <div key={field.id}>
          <label 
            className={`block mb-2 ${field.required ? "before:content-['*'] before:text-red-500 before:mr-1" : ""}`}
          >
            {field.label}
          </label>
          <input
            type="text"
            name={field.name}
            className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
          />
        </div>
      ))}
      <button className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600">
        Submit
      </button>
    </form>
  );
};

export default RegistrationForm;

Feature List with Checkmarks

A feature list component that adds checkmark icons using content-['✓'] before pseudo-element.

This is a live editor. Play around with it!
const FeatureList = () => {
  const features = [
    {
      id: 1,
      title: "Unlimited Storage",
      description: "Store as many files as you need with no limitations"
    },
    {
      id: 2,
      title: "Real-time Collaboration",
      description: "Work together with your team in real-time"
    },
    {
      id: 3,
      title: "Version History",
      description: "Access previous versions of your files anytime"
    },
    {
      id: 4,
      title: "Advanced Security",
      description: "Enterprise-grade security and encryption"
    },
    {
      id: 5,
      title: "24/7 Support",
      description: "Get help whenever you need it"
    },
    {
      id: 6,
      title: "API Access",
      description: "Integrate with your existing tools and workflows"
    }
  ];

  return (
    <div className="max-w-3xl mx-auto p-8">
      <div className="grid grid-cols-2 gap-8">
        {features.map(feature => (
          <div
            key={feature.id}
            className="before:content-['✓'] before:mr-3 before:text-green-500 before:font-bold flex items-start"
          >
            <div>
              <h3 className="font-semibold text-lg mb-2">{feature.title}</h3>
              <p className="text-gray-600">{feature.description}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default FeatureList;

A social media links component that adds icons using content-['→'] before pseudo-element with hover effects.

This is a live editor. Play around with it!
const SocialLinks = () => {
  const socialLinks = [
    {
      id: 1,
      platform: "Twitter",
      username: "@johndoe",
      url: "https://twitter.com/johndoe"
    },
    {
      id: 2,
      platform: "Instagram",
      username: "@johndoe",
      url: "https://instagram.com/johndoe"
    },
    {
      id: 3,
      platform: "LinkedIn",
      username: "John Doe",
      url: "https://linkedin.com/in/johndoe"
    },
    {
      id: 4,
      platform: "GitHub",
      username: "@johndoe",
      url: "https://github.com/johndoe"
    },
    {
      id: 5,
      platform: "Dribbble",
      username: "@johndoe",
      url: "https://dribbble.com/johndoe"
    },
    {
      id: 6,
      platform: "Medium",
      username: "@johndoe",
      url: "https://medium.com/@johndoe"
    }
  ];

  return (
    <div className="max-w-md mx-auto p-6 bg-gray-50 rounded-xl">
      {socialLinks.map(link => (
        <a
          key={link.id} 
          href={link.url}
          target="_blank"
          rel="noopener noreferrer"
          className="flex items-center p-4 hover:bg-gray-100 rounded-lg mb-2 group"
        >
          <span className="before:content-['→'] before:mr-3 before:text-gray-400 before:transition-transform before:inline-block group-hover:before:translate-x-1">
            <span className="font-medium">{link.platform}</span>
          </span>
          <span className="ml-auto text-gray-500">{link.username}</span>
        </a>
      ))}
    </div>
  );
};

export default SocialLinks;

A breadcrumb component that adds custom arrow separator using content-['→'] after pseudo-element.

This is a live editor. Play around with it!
const Breadcrumbs = () => {
  const paths = [
    { id: 1, name: "Home", url: "/" },
    { id: 2, name: "Electronics", url: "/electronics" },
    { id: 3, name: "Computers", url: "/electronics/computers" },
    { id: 4, name: "Laptops", url: "/electronics/computers/laptops" },
    { id: 5, name: "Gaming", url: "/electronics/computers/laptops/gaming" },
    { id: 6, name: "ASUS ROG", url: "/electronics/computers/laptops/gaming/asus-rog" }
  ];

  return (
    <nav className="p-4 bg-gray-50">
      <div className="flex flex-wrap items-center">
        {paths.map((path, index) => (
          <a
            key={path.id}
            href={path.url}
            className={`text-sm ${
              index === paths.length - 1
                ? "text-gray-600"
                : "text-blue-500 hover:text-blue-600"
            } ${
              index !== paths.length - 1
                ? "after:content-['→'] after:mx-2 after:text-gray-400 after:text-xs after:relative after:top-[1px]"
                : ""
            }`}
          >
            {path.name}
          </a>
        ))}
      </div>
    </nav>
  );
};

export default Breadcrumbs;

Customization Examples

Animated Progress Steps

An animated progress indicator with custom step markers and transitions.

This is a live editor. Play around with it!
// App.js
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;
  
const ProgressSteps = () => {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <div className="relative flex justify-between">
        {[1, 2, 3, 4].map((step) => (
          <div key={step} 
               className="relative z-10 flex flex-col items-center">
            <div className="w-10 h-10 rounded-full bg-white border-2
                          border-gray-300 flex items-center justify-center
                          before:content-todo before:text-gray-400
                          data-[status=active]:before:content-in-progress
                          data-[status=active]:before:text-blue-500
                          data-[status=active]:border-blue-500
                          data-[status=complete]:before:content-done
                          data-[status=complete]:before:text-green-500
                          data-[status=complete]:border-green-500"
                 data-status={step === 2 ? 'active' : step < 2 ? 'complete' : 'todo'}>
            </div>
            <div className="absolute -bottom-8 whitespace-nowrap
                          text-sm text-gray-500
                          before:content-processing
                          data-[status=complete]:before:content-completed
                          data-[status=complete]:before:text-green-500"
                 data-status={step < 3 ? 'complete' : 'todo'}>
            </div>
          </div>
        ))}
        <div className="absolute top-5 left-0 h-[2px] bg-gray-300 w-full -z-10">
          <div className="h-full bg-green-500 transition-all duration-500"
               style={{ width: '50%' }}>
          </div>
        </div>
      </div>
    </div>
  );
};

export default ProgressSteps;

Social Card with Icon Decorators

A social card with custom icon decorators using content utility.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;
  
const SocialCard = () => {
  return (
    <div className="max-w-lg mx-auto bg-white rounded-xl shadow-lg overflow-hidden">
      <div className="relative">
        <img 
          src="https://images.unsplash.com/photo-1672243777342-0698e84a41fc"
          alt="Post image" 
          className="w-full h-64 object-cover"
        />
        <button className="absolute top-4 right-4 w-8 h-8 rounded-full 
                         bg-black/30 backdrop-blur-sm
                         before:content-menu before:text-white 
                         hover:bg-black/50 transition-colors">
        </button>
        <button className="absolute top-4 left-4 
                         before:content-bookmark before:text-2xl
                         hover:scale-110 transition-transform">
        </button>
      </div>

      <div className="p-6">
        <div className="flex items-center gap-3 mb-4">
          <div className="relative">
            <img 
              src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde"
              alt="Avatar"
              className="w-10 h-10 rounded-full"
            />
            <span className="absolute -right-1 -bottom-1 
                          before:content-verified before:text-xs
                          before:bg-blue-500 before:text-white
                          before:w-4 before:h-4 before:rounded-full
                          before:flex before:items-center before:justify-center">
            </span>
          </div>
          <div>
            <h3 className="font-semibold">Alex Morgan</h3>
            <p className="text-sm text-gray-600">Photographer</p>
          </div>
        </div>

        <div className="flex items-center justify-between mt-4 pt-4 border-t">
          <div className="flex gap-6">
            <button className="group flex items-center gap-2">
              <span className="before:content-heart 
                            before:text-gray-600 
                            group-hover:before:text-red-500
                            before:transition-colors">
              </span>
              <span className="text-sm">2.4k</span>
            </button>
            <button className="group flex items-center gap-2">
              <span className="before:content-comment
                            before:text-gray-600
                            group-hover:before:text-blue-500
                            before:transition-colors">
              </span>
              <span className="text-sm">482</span>
            </button>
            <button className="group flex items-center gap-2">
              <span className="before:content-share
                            before:text-gray-600
                            group-hover:before:text-green-500
                            before:transition-colors">
              </span>
              <span className="text-sm">Share</span>
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default SocialCard;

File Card with Status Icons

A file card using content utility for status and action icons

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;
  
const FileCard = () => {
  return (
    <div className="max-w-md mx-auto">
      <div className="bg-white rounded-xl shadow-lg p-6 hover:shadow-xl transition-shadow">
        <div className="flex items-start justify-between">
          <div className="flex items-start gap-4">
            <div className="w-12 h-12 bg-blue-50 rounded-lg
                          flex items-center justify-center
                          before:content-pdf before:text-2xl">
            </div>
            <div>
              <h3 className="font-semibold text-gray-900">Annual Report 2024.pdf</h3>
              <p className="text-sm text-gray-500 mt-1">4.2 MB • Modified 2 days ago</p>
              <div className="flex items-center gap-2 mt-2">
                <span className="inline-flex items-center gap-1 px-2 py-1 
                              bg-green-50 text-green-700 text-xs rounded-full">
                  <span className="before:content-check before:text-xs"></span>
                  Signed
                </span>
              </div>
            </div>
          </div>

          <div className="flex items-center gap-2">
            <button className="w-8 h-8 rounded-lg bg-gray-50
                           flex items-center justify-center group
                           hover:bg-blue-50 transition-colors">
              <span className="before:content-download
                            before:text-gray-600
                            group-hover:before:text-blue-500
                            before:transition-colors">
              </span>
            </button>
            <button className="w-8 h-8 rounded-lg bg-gray-50
                           flex items-center justify-center group
                           hover:bg-blue-50 transition-colors">
              <span className="before:content-share
                            before:text-gray-600
                            group-hover:before:text-blue-500
                            before:transition-colors">
              </span>
            </button>
            <button className="w-8 h-8 rounded-lg bg-gray-50
                           flex items-center justify-center group
                           hover:bg-blue-50 transition-colors">
              <span className="before:content-more
                            before:text-gray-600
                            group-hover:before:text-blue-500
                            before:transition-colors">
              </span>
            </button>
          </div>
        </div>

        <div className="mt-4 pt-4 border-t border-gray-100">
          <div className="flex items-center gap-3">
            <img 
              src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde"
              alt="Owner"
              className="w-6 h-6 rounded-full"
            />
            <span className="text-sm text-gray-600">
              Shared by Alex Morgan
            </span>
          </div>
        </div>
      </div>
    </div>
  );
};

export default FileCard;

Best Practices

Maintain Design Consistency

Using content-* utilities in Tailwind CSS effectively requires a structured approach to maintain design consistency. These utilities allow for inserting generated content before or after elements using before:content-[''] or after:content-[''].

Ensuring a uniform look across a project involves adhering to predefined design patterns and avoiding inconsistent use of content insertion. For example, when using the content utility to add icons, indicators, helper text, etc., it is best to follow a uniform typography, size, and color scheme to avoid discrepancies.

Optimize for Reusability

Reusability is critical when working with content-* utilities, especially in component-driven development. By default, Tailwind only provides content-none, so custom content utilities must be defined in the Tailwind configuration. Using the extend property within the theme section of tailwind.config.js, developers can add custom content utilities for frequent use.

For instance, defining custom utilities such as before:content-['✓'] or after:content-['→'] ensures that commonly used pseudo-elements are standardized across the project. This eliminates the need for inline styles, reducing redundancy and improving maintainability.

Accessibility Considerations

Enhance Readability and Navigability

Readability is a critical factor when using content-* utilities. Dynamically inserted content should always complement, rather than obscure, surrounding text. Using appropriate contrast, spacing, and font sizes ensures that the added content remains legible for all users.

For navigability, content insertion should not disrupt logical content flow. If before:content-[''] is applied to headings or paragraphs, ensuring appropriate margin spacing with before:mr-2 prevents visual distractions.

Keyboard navigation should also be factored in. Since pseudo-elements are not focusable by default, they should be used only for decorative purposes rather than essential interactive elements.

Support Accessible Interactive Elements

Interactive elements should not rely solely on content-* utilities for functionality. Since pseudo-elements (::before and ::after) are not part of the DOM in a way that allows user interaction, they cannot receive focus or click. This means that any critical functionality—such as navigation, form actions, or buttons—should not be dependent on content-* for essential behavior. Instead, pseudo-elements should be treated as visual enhancements to improve aesthetics without affecting usability.

For example, a breadcrumb navigation can use before:content-['→'] to add a separator, but the actual clickable links should still be standard <a> elements for proper focusability and accessibility. Similarly, a list styled with after:content-['✓'] should still contain real text.