Menu

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.

ClassPropertiesExample
border-collapseborder-collapse: collapse;<div className="border-collapse"></div>
border-separateborder-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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.