Tailwind CSS Border Collapse
Border collapse is a concept in CSS that applies to tables, determining how the borders between adjacent table cells are managed. By default, table elements render with separated borders, but the border-collapse
property lets you merge adjacent cell borders (collapse
) or maintain their default separation (separate
).
Tailwind CSS provides a collection of utilities for easily managing border collapse properties. This guide explores how to control border collapse styles in your projects using Tailwind CSS, including basic implementations, conditional states, and breakpoints.
Class | Properties | Example |
---|---|---|
border-collapse | border-collapse: collapse; | <div className="border-collapse"></div> |
border-separate | border-collapse: separate; | <div className="border-separate"></div> |
Overview of Border Collapse
In Tailwind CSS, you can toggle between collapsed borders and separate borders using straightforward utility classes. These utilities let you control whether adjacent table cell borders fuse or remain distinct. A collapsed border eliminates the space between adjacent cells, improving visual consistency in organized data tables.
Collapsing the Borders
Use the border-collapse
utility to merge adjacent table cell borders. This utility ensures a streamlined appearance when crafting data-heavy tables.
export default function CollapsedTable() { return ( <div className="w-screen h-screen bg-gray-100 flex items-center justify-center"> <table className="table-auto border-collapse border border-gray-400"> {/* Set border-collapse: collapse */} <thead> <tr> <th className="border border-gray-300 p-4 bg-gray-200">Column 1</th> <th className="border border-gray-300 p-4 bg-gray-200">Column 2</th> </tr> </thead> <tbody> <tr> <td className="border border-gray-400 p-2">Data 1</td> <td className="border border-gray-400 p-2">Data 2</td> </tr> <tr> <td className="border border-gray-400 p-2">Data 3</td> <td className="border border-gray-400 p-2">Data 4</td> </tr> </tbody> </table> </div> ); }
Separating the Borders
To allow spacing between table cell borders, apply the border-separate
utility. This is especially helpful when you want to add distinct visual spacings.
export default function SeparatedTable() { return ( <div className="w-screen h-screen bg-gray-100 flex items-center justify-center"> <table className="table-auto border-separate border border-gray-400"> {/* Set border-collapse: separate */} <thead> <tr> <th className="border border-gray-300 p-4 bg-gray-200">Column 1</th> <th className="border border-gray-300 p-4 bg-gray-200">Column 2</th> </tr> </thead> <tbody> <tr> <td className="border border-gray-400 p-2">Data 1</td> <td className="border border-gray-400 p-2">Data 2</td> </tr> <tr> <td className="border border-gray-400 p-2">Data 3</td> <td className="border border-gray-400 p-2">Data 4</td> </tr> </tbody> </table> </div> ); }
States and Responsiveness
Applying border-collapse
conditionally allows you to dynamically adjust table styles based on interaction states or viewport sizes. Tailwind’s utility-first approach makes this simple.
Hover and Focus States
You can toggle between collapsed and separate borders based on hover
or focus
interactions. This is particularly effective for interactive data tables.
export default function InteractiveTable() { return ( <div className="w-screen h-screen bg-gray-100 flex items-center justify-center"> <table className="table-auto border-collapse hover:border-separate border border-gray-400"> {/* border-collapse changes to border-separate on hover */} <thead> <tr> <th className="border border-gray-300 p-4 bg-gray-200">Column 1</th> <th className="border border-gray-300 p-4 bg-gray-200">Column 2</th> </tr> </thead> <tbody> <tr> <td className="border border-gray-400 p-2">Data 1</td> <td className="border border-gray-400 p-2">Data 2</td> </tr> <tr> <td className="border border-gray-400 p-2">Data 3</td> <td className="border border-gray-400 p-2">Data 4</td> </tr> </tbody> </table> </div> ); }
Breakpoint Modifiers
Responsive design is critical for modern web applications. Tailwind lets you use breakpoints (like sm
, md
, lg
) to adapt table border styles to different device sizes.
export default function ResponsiveTable() { return ( <div className="w-screen h-screen bg-gray-100 flex items-center justify-center"> <table className="table-auto sm:border-separate md:border-collapse lg:border-separate border border-gray-400"> {/* Adjust border-collapse styles for different breakpoints */} <thead> <tr> <th className="border border-gray-300 p-4 bg-gray-200">Column 1</th> <th className="border border-gray-300 p-4 bg-gray-200">Column 2</th> </tr> </thead> <tbody> <tr> <td className="border border-gray-400 p-2">Data 1</td> <td className="border border-gray-400 p-2">Data 2</td> </tr> <tr> <td className="border border-gray-400 p-2">Data 3</td> <td className="border border-gray-400 p-2">Data 4</td> </tr> </tbody> </table> </div> ); }
Real World Examples
Financial Report Table
A professional financial report table showing quarterly earnings with color-coded indicators for performance metrics.
export default function FinancialReport() { const financialData = [ { department: "Sales", q1: "1.2M", q2: "1.5M", q3: "1.3M", q4: "1.8M", growth: "+15%", status: "positive" }, { department: "Marketing", q1: "400K", q2: "450K", q3: "480K", q4: "520K", growth: "+12%", status: "positive" }, { department: "Research", q1: "300K", q2: "280K", q3: "260K", q4: "310K", growth: "-5%", status: "negative" }, { department: "Operations", q1: "800K", q2: "820K", q3: "850K", q4: "900K", growth: "+8%", status: "positive" }, { department: "IT Infrastructure", q1: "600K", q2: "580K", q3: "590K", q4: "620K", growth: "+2%", status: "neutral" }, { department: "Customer Support", q1: "250K", q2: "280K", q3: "300K", q4: "320K", growth: "+10%", status: "positive" } ]; return ( <div className="p-8 bg-gray-50"> <table className="w-full border-collapse bg-white shadow-xl rounded-lg overflow-hidden"> <thead> <tr className="bg-blue-900 text-white"> <th className="border border-blue-800 p-4 text-left">Department</th> <th className="border border-blue-800 p-4 text-right">Q1</th> <th className="border border-blue-800 p-4 text-right">Q2</th> <th className="border border-blue-800 p-4 text-right">Q3</th> <th className="border border-blue-800 p-4 text-right">Q4</th> <th className="border border-blue-800 p-4 text-right">YoY Growth</th> </tr> </thead> <tbody> {financialData.map((item, index) => ( <tr key={index} className="hover:bg-blue-50"> <td className="border border-gray-200 p-4 font-medium">{item.department}</td> <td className="border border-gray-200 p-4 text-right">{item.q1}</td> <td className="border border-gray-200 p-4 text-right">{item.q2}</td> <td className="border border-gray-200 p-4 text-right">{item.q3}</td> <td className="border border-gray-200 p-4 text-right">{item.q4}</td> <td className={`border border-gray-200 p-4 text-right font-bold ${ item.status === 'positive' ? 'text-green-600' : item.status === 'negative' ? 'text-red-600' : 'text-yellow-600' }`}> {item.growth} </td> </tr> ))} </tbody> </table> </div> ); }
Product Description Table
A product description table that uses border-separate to create a clean, organized layout for comparing features.
export default function ProductComparison() { const products = [ { id: 1, name: "Premium Laptop", price: "$1299", image: "https://images.unsplash.com/photo-1496181133206-80ce9b88a853", specs: { processor: "Intel i7", ram: "16GB", storage: "512GB SSD", display: "15.6 inch 4K", battery: "10 hours" } }, ]; return ( <div className="max-w-6xl mx-auto p-4"> <div className="overflow-x-auto"> <table className="w-full border-separate"> <thead> <tr className="bg-blue-50"> <th className="border border-blue-200 p-4"></th> {products.map(product => ( <th key={product.id} className="border border-blue-200 p-4"> <div className="space-y-2"> <img src={product.image} alt={product.name} className="w-32 h-32 object-cover mx-auto" /> <h3 className="font-bold">{product.name}</h3> <p className="text-blue-600">{product.price}</p> </div> </th> ))} </tr> </thead> <tbody> {Object.keys(products[0].specs).map(spec => ( <tr key={spec} className="hover:bg-gray-50"> <td className="border border-blue-200 p-4 font-medium capitalize"> {spec} </td> {products.map(product => ( <td key={product.id} className="border border-blue-200 p-4 text-center"> {product.specs[spec]} </td> ))} </tr> ))} </tbody> </table> </div> </div> ); }
Product Comparison Table
A responsive table showing product details with collapsed borders for a clean look. Perfect for e-commerce product comparison pages.
export default function ProductComparisonTable() { const products = [ { price: "$89.99", rating: "4.8/5", image: "https://images.unsplash.com/photo-1627123424574-724758594e93", availability: "In Stock", shipping: "Free" }, { price: "$129.99", rating: "4.6/5", image: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62", availability: "Limited Stock", shipping: "$4.99" }, { price: "$199.99", rating: "4.9/5", image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e", availability: "In Stock", shipping: "Free" }, { price: "$299.99", rating: "4.7/5", image: "https://images.unsplash.com/photo-1579586337278-3befd40fd17a", availability: "Pre-order", shipping: "Free" }, { price: "$159.99", rating: "4.5/5", image: "https://images.unsplash.com/photo-1511467687858-23d96c32e4ae", availability: "In Stock", shipping: "$9.99" }, { price: "$79.99", rating: "4.4/5", image: "https://images.unsplash.com/photo-1527864550417-7fd91fc51a46", availability: "In Stock", shipping: "Free" } ]; return ( <div className="overflow-x-auto"> <table className="w-full border-collapse border border-gray-200"> <thead className="bg-gray-100"> <tr> <th className="p-4 border border-gray-200">Product</th> <th className="p-4 border border-gray-200">Price</th> <th className="p-4 border border-gray-200">Rating</th> <th className="p-4 border border-gray-200">Availability</th> <th className="p-4 border border-gray-200">Shipping</th> </tr> </thead> <tbody> {products.map((product) => ( <tr key={product.name}> <td className="p-4 border border-gray-200"> <div className="flex items-center space-x-4"> <img src={product.image} alt={product.name} className="w-16 h-16 object-cover rounded"/> <span>{product.name}</span> </div> </td> <td className="p-4 border border-gray-200">{product.price}</td> <td className="p-4 border border-gray-200">{product.rating}</td> <td className="p-4 border border-gray-200">{product.availability}</td> <td className="p-4 border border-gray-200">{product.shipping}</td> </tr> ))} </tbody> </table> </div> ); }
Course Schedule Table
An academic course schedule table with time slots and room assignments.
export default function CourseSchedule() { const courses = [ { code: "CS101", name: "Introduction to Programming", instructor: "Dr. Smith", time: "9:00 AM - 10:30 AM", days: "Mon/Wed", room: "Tech-201", capacity: "45/50", status: "Available" }, { code: "MATH202", name: "Linear Algebra", instructor: "Prof. Johnson", time: "11:00 AM - 12:30 PM", days: "Tue/Thu", room: "Science-301", capacity: "50/50", status: "Full" }, { code: "PHY301", name: "Quantum Mechanics", instructor: "Dr. Brown", time: "2:00 PM - 3:30 PM", days: "Mon/Wed/Fri", room: "Physics-101", capacity: "28/30", status: "Available" }, { code: "ENG205", name: "Creative Writing", instructor: "Prof. Davis", time: "1:00 PM - 2:30 PM", days: "Tue/Thu", room: "Arts-401", capacity: "22/25", status: "Available" }, { code: "BIO404", name: "Molecular Biology", instructor: "Dr. Wilson", time: "3:00 PM - 4:30 PM", days: "Mon/Wed", room: "Bio-201", capacity: "35/35", status: "Full" }, { code: "CHEM303", name: "Organic Chemistry", instructor: "Prof. Anderson", time: "10:00 AM - 11:30 AM", days: "Mon/Wed/Fri", room: "Chem-101", capacity: "40/45", status: "Available" } ]; return ( <div className="p-6 bg-gray-50"> <table className="w-full border-separate bg-white shadow-lg rounded-lg"> <thead> <tr className="bg-green-700 text-white"> <th className="border border-green-600 p-3">Course Code</th> <th className="border border-green-600 p-3">Course Name</th> <th className="border border-green-600 p-3">Instructor</th> <th className="border border-green-600 p-3">Time</th> <th className="border border-green-600 p-3">Days</th> <th className="border border-green-600 p-3">Room</th> <th className="border border-green-600 p-3">Capacity</th> <th className="border border-green-600 p-3">Status</th> </tr> </thead> <tbody> {courses.map((course, index) => ( <tr key={index} className={` ${index % 2 === 0 ? 'bg-white' : 'bg-green-50'} hover:bg-green-100 transition-colors duration-150 `}> <td className="border border-gray-200 p-3 font-medium">{course.code}</td> <td className="border border-gray-200 p-3">{course.name}</td> <td className="border border-gray-200 p-3">{course.instructor}</td> <td className="border border-gray-200 p-3">{course.time}</td> <td className="border border-gray-200 p-3">{course.days}</td> <td className="border border-gray-200 p-3">{course.room}</td> <td className="border border-gray-200 p-3">{course.capacity}</td> <td className={`border border-gray-200 p-3 font-medium ${ course.status === 'Available' ? 'text-green-600' : 'text-red-600' }`}> {course.status} </td> </tr> ))} </tbody> </table> </div> ); }
Restaurant Menu Table
An elegant restaurant menu table with dish categories, prices, and dietary information.
export default function RestaurantMenu() { const menuItems = [ { name: "Grilled Salmon", category: "Mains", description: "Fresh Atlantic salmon with herbs and lemon butter sauce", price: "$28", dietary: ["GF", "DF"], image: "https://images.unsplash.com/photo-1485921325833-c519f76c4927", alt: "Grilled salmon with vegetables", popular: true }, { name: "Mushroom Risotto", category: "Mains", description: "Creamy Arborio rice with wild mushrooms and parmesan", price: "$24", dietary: ["V"], image: "https://images.unsplash.com/photo-1476124369491-e7addf5db371", alt: "Creamy mushroom risotto", popular: false }, { name: "Beef Tenderloin", category: "Mains", description: "8oz grass-fed beef with red wine reduction", price: "$34", dietary: ["GF"], image: "https://images.unsplash.com/photo-1558030006-450675393462", alt: "Beef tenderloin steak", popular: true }, { name: "Mediterranean Salad", category: "Starters", description: "Mixed greens, feta, olives, and balsamic dressing", price: "$16", dietary: ["V", "GF"], image: "https://images.unsplash.com/photo-1540420773420-3366772f4999", alt: "Fresh Mediterranean salad", popular: false }, { name: "Chocolate Fondant", category: "Desserts", description: "Warm chocolate cake with vanilla ice cream", price: "$12", dietary: ["V"], image: "https://images.unsplash.com/photo-1542124948-dc391252a940", alt: "Chocolate fondant dessert", popular: true }, { name: "Seafood Linguine", category: "Mains", description: "Fresh pasta with mixed seafood in white wine sauce", price: "$30", dietary: [], image: "https://images.unsplash.com/photo-1563379926898-05f4575a45d8", alt: "Seafood linguine pasta", popular: false } ]; return ( <div className="p-8 bg-amber-50"> <table className="w-full border-collapse bg-white shadow-xl rounded-lg"> <thead> <tr className="bg-amber-800 text-amber-50"> <th className="border border-amber-700 p-4">Dish</th> <th className="border border-amber-700 p-4">Category</th> <th className="border border-amber-700 p-4">Image</th> <th className="border border-amber-700 p-4">Description</th> <th className="border border-amber-700 p-4">Price</th> <th className="border border-amber-700 p-4">Dietary</th> </tr> </thead> <tbody> {menuItems.map((item, index) => ( <tr key={index} className={` ${item.popular ? 'bg-amber-50' : 'bg-white'} hover:bg-amber-100 transition-colors duration-150 `}> <td className="border border-amber-100 p-4 font-medium"> {item.name} {item.popular && ( <span className="ml-2 text-amber-600 text-sm">★ Popular</span> )} </td> <td className="border border-amber-100 p-4">{item.category}</td> <td className="border border-amber-100 p-4"> <img src={item.image} alt={item.alt} className="w-24 h-24 object-cover rounded" /> </td> <td className="border border-amber-100 p-4">{item.description}</td> <td className="border border-amber-100 p-4 font-medium">{item.price}</td> <td className="border border-amber-100 p-4"> <div className="flex gap-1"> {item.dietary.map((diet, i) => ( <span key={i} className="px-2 py-1 bg-amber-100 text-amber-800 text-xs rounded"> {diet} </span> ))} </div> </td> </tr> ))} </tbody> </table> <div className="mt-4 text-amber-800 text-sm"> <span className="mr-4">V - Vegetarian</span> <span className="mr-4">GF - Gluten Free</span> <span>DF - Dairy Free</span> </div> </div> ); }
Best Practices
Maintain Design Consistency
When working with the border-collapse
utilities in Tailwind CSS, it's crucial to ensure a uniform style throughout your project. Using border-collapse
thoughtfully across your data tables and layouts can help create predictable and intuitive designs. For instance, choose border-collapse
for compact, professional layouts and border-separate
for visual clarity. Your decision should align with the project's overall aesthetic and usability goals.
Optimize for Reusability
Tailwind CSS is inherently utility-first, but that doesn’t mean you can’t prioritize reusability in your components. Use the border-collapse
utilities in conjunction with component-based table layouts to reduce redundancy and maintain clean, reusable code. Break down your tables into reusable JSX components and pass parameters, such as border styles, as props.
For example, you can define a reusable table component for different business logic scenarios. By building standardized table components preconfigured with border-collapse
and border-separate
, you ensure a consistent output while reducing repetitive coding overhead, especially in dynamic projects.
Accessibility Considerations
Enhance Readability and Navigability
The way you structure tables with border-collapse
affects readability and ease of navigation, particularly for users reliant on assistive technologies. Collapsed borders often reduce visual noise, making data-laden tables easier to scan and understand. Use sufficient spacing or padding between cells alongside collapsed borders for enhanced clarity.
For example, you can apply padding utilities like p-2
to make cell content more distinguishable. This is especially necessary for tables containing dense data or nested elements.
Focus on High Contrast
To improve accessibility, ensure adequate contrast between your tables' borders and their background colors. This is especially important when using border-collapse
to merge cell edges, as low contrast can make the boundaries indistinguishable for visually impaired users. Use Tailwind's border color utilities with appropriate background colors to achieve compliant contrast ratios.
When designing dark mode themes, maintain borders and backgrounds that provide sufficient separation. For instance, dark backgrounds (bg-gray-900
) pair well with light borders (border-gray-300
) to ensure clarity.