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.
Class | Properties | Example |
---|---|---|
content-none | content: 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.
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:
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"]
.
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:
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:
.
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:
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.
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.
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.
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.
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;
Social Links with Icons
A social media links component that adds icons using content-['→']
before pseudo-element with hover effects.
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;
Breadcrumb Navigation
A breadcrumb component that adds custom arrow separator using content-['→']
after pseudo-element.
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.
// 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.
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
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.