Styling Tables
Default Browser Table Styles
HTML tables come with minimal default styling from the browser. By default, tables have no visible borders on cells, no background colors, and inconsistent spacing. The default rendering separates each cell with a small gap, text aligns to the left in <td> elements and centers in <th> elements, and header cells are rendered bold. This bare-bones rendering is functional but visually unappealing for any modern web application. Understanding these defaults is the first step toward building polished, professional data tables.
When you create a table in HTML without any CSS, the browser applies its user-agent stylesheet. This stylesheet varies slightly between browsers, but the general behavior is consistent: cells are spaced apart, borders are not visible, and padding is minimal. Your job as a developer is to override these defaults to create tables that are readable, attractive, and responsive.
A Basic Unstyled HTML Table
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Department</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice Johnson</td>
<td>Developer</td>
<td>Engineering</td>
<td>$95,000</td>
</tr>
<tr>
<td>Bob Smith</td>
<td>Designer</td>
<td>Creative</td>
<td>$82,000</td>
</tr>
</tbody>
</table>
Without CSS, this table renders as plain text with cells separated by invisible gaps. Let us now explore how to transform this into a well-styled data table step by step.
border-collapse vs border-separate
The border-collapse property is one of the most important CSS properties for table styling. It controls whether adjacent table cells share borders or have their own individual borders with gaps between them. This property is applied to the <table> element itself and affects all cells within it.
There are two possible values:
- border-separate (default) -- Each cell has its own individual border. Adjacent cells have a visible gap between their borders. This is the browser default behavior.
- border-collapse -- Adjacent cell borders merge into a single border. There is no gap between cells. This produces the clean, grid-like appearance that most developers expect from tables.
border-collapse: separate (Default)
table {
border-collapse: separate;
border: 2px solid #333;
}
th, td {
border: 1px solid #999;
padding: 10px 15px;
}
/* Result: Each cell has its own border with visible gaps
between adjacent cells. Borders appear doubled where
cells meet each other. */
border-collapse: collapse
table {
border-collapse: collapse;
border: 2px solid #333;
}
th, td {
border: 1px solid #999;
padding: 10px 15px;
}
/* Result: Adjacent borders merge into a single line.
No gaps between cells. Clean grid appearance.
This is the most common setting for data tables. */
border-collapse: collapse, the border-spacing property has no effect. The two properties are mutually exclusive in practice -- you use border-spacing only with border-separate.When borders collapse, conflicts can arise when adjacent cells have different border styles. CSS resolves these conflicts using a set of rules: wider borders win over narrower ones, solid borders win over dashed or dotted borders, and cell borders win over row borders, which win over table borders. Understanding this hierarchy helps you predict how your table will render.
The border-spacing Property
The border-spacing property controls the gap between adjacent cell borders when border-collapse is set to separate. It accepts one or two length values -- one value sets equal horizontal and vertical spacing, while two values set horizontal and vertical spacing independently.
Using border-spacing
/* Equal spacing in all directions */
table {
border-collapse: separate;
border-spacing: 10px;
}
/* Different horizontal and vertical spacing */
table {
border-collapse: separate;
border-spacing: 15px 8px; /* horizontal vertical */
}
/* Zero spacing (cells touch but borders do not merge) */
table {
border-collapse: separate;
border-spacing: 0;
}
/* Creative use: card-style table rows */
table.card-table {
border-collapse: separate;
border-spacing: 0 12px; /* no horizontal gap, 12px vertical gap */
}
table.card-table td {
background: #fff;
padding: 16px 20px;
border-top: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
table.card-table td:first-child {
border-left: 1px solid #e0e0e0;
border-radius: 8px 0 0 8px;
}
table.card-table td:last-child {
border-right: 1px solid #e0e0e0;
border-radius: 0 8px 8px 0;
}
border-spacing: 0 12px is a popular modern design technique. By adding vertical spacing between rows and rounding the corners of the first and last cells, you create rows that look like individual cards. This approach works well for dashboards and admin panels.Styling Table Headers and Cells
Table headers (<th>) and data cells (<td>) are the core building blocks of table appearance. Effective table styling creates a clear visual hierarchy that helps users scan and understand the data quickly. Headers should stand out from data cells through differences in background color, font weight, text color, and sometimes text transform.
Comprehensive Header and Cell Styling
table {
border-collapse: collapse;
width: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.5;
}
/* Header styling */
thead th {
background-color: #1a1a2e;
color: #ffffff;
font-weight: 600;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.05em;
padding: 14px 18px;
text-align: left;
border-bottom: 3px solid #e94560;
position: sticky;
top: 0;
z-index: 10;
}
/* Data cell styling */
tbody td {
padding: 12px 18px;
border-bottom: 1px solid #e8e8e8;
color: #333;
vertical-align: middle;
}
/* Footer styling */
tfoot td {
background-color: #f0f0f0;
font-weight: 700;
padding: 14px 18px;
border-top: 2px solid #ccc;
}
Notice the use of position: sticky on the header. This keeps the header visible when users scroll through long tables, which is essential for usability. The top: 0 ensures the header sticks to the top of its scrolling container, and z-index: 10 ensures it stays above the table body content.
Column-Specific Styling
/* Numeric columns should be right-aligned */
td.numeric,
th.numeric {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* Status columns with colored badges */
td.status .badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
td.status .badge--active {
background-color: #d4edda;
color: #155724;
}
td.status .badge--inactive {
background-color: #f8d7da;
color: #721c24;
}
td.status .badge--pending {
background-color: #fff3cd;
color: #856404;
}
font-variant-numeric: tabular-nums on columns that contain numbers. This forces the browser to use fixed-width digits, so numbers in a column align perfectly vertically regardless of their individual digit values. This dramatically improves readability of financial and statistical data.Striped Rows with nth-child
Zebra striping -- alternating row background colors -- is one of the most effective ways to improve table readability. When users scan a wide table horizontally, striped rows help their eyes stay on the correct row. CSS makes this trivial with the :nth-child pseudo-class selector.
Basic Zebra Striping
/* Stripe even rows */
tbody tr:nth-child(even) {
background-color: #f8f9fa;
}
/* Or stripe odd rows instead */
tbody tr:nth-child(odd) {
background-color: #f8f9fa;
}
/* Colored striping for themed tables */
tbody tr:nth-child(even) {
background-color: rgba(0, 123, 255, 0.05);
}
/* Stronger contrast striping */
tbody tr:nth-child(even) {
background-color: #e9ecef;
}
tbody tr:nth-child(odd) {
background-color: #ffffff;
}
The choice between striping even or odd rows depends on your design. If your table body has a white background, striping even rows with a light gray creates a natural alternating pattern starting with white for the first row.
Advanced nth-child Patterns
/* Stripe every 3rd row */
tbody tr:nth-child(3n) {
background-color: #f0f7ff;
}
/* Group stripes: 2 white, 2 colored */
tbody tr:nth-child(4n+1),
tbody tr:nth-child(4n+2) {
background-color: #ffffff;
}
tbody tr:nth-child(4n+3),
tbody tr:nth-child(4n) {
background-color: #f5f5f5;
}
/* Column striping (less common but useful) */
td:nth-child(even) {
background-color: rgba(0, 0, 0, 0.02);
}
/* Highlight a specific column */
td:nth-child(3),
th:nth-child(3) {
background-color: #fff8e1;
font-weight: 600;
}
Hover Effects on Table Rows
Row hover effects provide immediate visual feedback as users move their cursor over data rows. This is particularly important in wide tables where it can be difficult to track which row you are reading. A well-implemented hover effect highlights the entire row, making it stand out clearly from surrounding rows.
Table Row Hover Effects
/* Basic row hover */
tbody tr:hover {
background-color: #e8f4fd;
}
/* Smooth transition hover */
tbody tr {
transition: background-color 0.15s ease;
}
tbody tr:hover {
background-color: #e3f2fd;
}
/* Hover with left border accent */
tbody tr {
transition: all 0.15s ease;
border-left: 3px solid transparent;
}
tbody tr:hover {
background-color: #f5f5f5;
border-left: 3px solid #2196f3;
}
/* Cell-level hover for data exploration */
tbody td {
transition: background-color 0.1s ease;
}
tbody td:hover {
background-color: #bbdefb;
cursor: pointer;
}
/* Clickable row styling */
tbody tr.clickable {
cursor: pointer;
transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
tbody tr.clickable:hover {
background-color: #e8f5e9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
Combining Stripes and Hover
/* The key: hover must override stripe colors */
tbody tr:nth-child(even) {
background-color: #f8f9fa;
}
tbody tr:nth-child(odd) {
background-color: #ffffff;
}
/* Hover overrides both stripe colors */
tbody tr:hover {
background-color: #d4e6f1 !important;
}
/* Better approach without !important -- use higher specificity */
tbody tr:nth-child(even):hover,
tbody tr:nth-child(odd):hover {
background-color: #d4e6f1;
}
!important to override stripe colors on hover. Instead, increase selector specificity by combining :nth-child with :hover. This keeps your CSS maintainable and avoids specificity wars down the road.Caption Styling and caption-side
The <caption> element provides a title or description for a table. It is an important accessibility feature because screen readers announce the caption when they encounter a table, giving users context about the data before they navigate through cells. The caption-side property controls whether the caption appears above or below the table.
Styling Table Captions
<table>
<caption>Quarterly Sales Report -- Q4 2024</caption>
<thead>
<tr>
<th>Region</th>
<th>Revenue</th>
<th>Growth</th>
</tr>
</thead>
<!-- ... -->
</table>
caption-side Property
/* Caption above the table (default) */
caption {
caption-side: top;
text-align: left;
font-size: 18px;
font-weight: 700;
color: #1a1a2e;
padding: 12px 0;
margin-bottom: 8px;
}
/* Caption below the table */
caption {
caption-side: bottom;
text-align: right;
font-size: 13px;
font-style: italic;
color: #777;
padding: 10px 0;
}
/* Styled caption with decoration */
caption {
caption-side: top;
text-align: left;
font-size: 20px;
font-weight: 700;
color: #2c3e50;
padding: 16px 0;
border-bottom: 3px solid #3498db;
margin-bottom: 0;
}
<caption> element instead of placing a heading before the table. The <caption> element is semantically associated with the table, which benefits screen readers and other assistive technologies. Even if you visually hide the caption, it should still be present in the markup for accessibility.Column Group Styling with colgroup and col
The <colgroup> and <col> elements allow you to apply styles to entire columns without adding classes to every cell. This is a powerful HTML feature that CSS can leverage for efficient column-wide styling. However, only a limited set of CSS properties work on <col> elements: background, border, width, and visibility.
Using colgroup and col
<table>
<colgroup>
<col class="col-name" style="width: 30%;">
<col class="col-role" style="width: 25%;">
<col class="col-dept" style="width: 25%;">
<col class="col-salary" style="width: 20%;">
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Department</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<!-- rows here -->
</tbody>
</table>
Styling Columns with CSS
/* Highlight a column */
col.col-salary {
background-color: #fffde7;
}
/* Set column widths */
col.col-name { width: 200px; }
col.col-role { width: 150px; }
col.col-dept { width: 150px; }
col.col-salary { width: 120px; }
/* Using span to group columns */
/* In HTML:
<colgroup>
<col span="2" style="background-color: #f0f8ff;">
<col span="2" style="background-color: #fff8f0;">
</colgroup>
*/
<col> element supports only a small subset of CSS properties: background, border, width, and visibility. Properties like color, font-size, padding, and text-align do not work on <col> elements. For those, you need to target cells directly using td:nth-child() selectors.table-layout: fixed vs auto
The table-layout property controls the algorithm the browser uses to calculate column widths. This has a significant impact on both rendering performance and visual layout, especially for large tables with many rows of data.
- table-layout: auto (default) -- The browser examines the content of every cell in every row to determine the optimal column widths. This produces the best-fitting layout but is slower because the browser must download and analyze the entire table before it can render anything.
- table-layout: fixed -- The browser determines column widths based only on the first row of cells (or explicit width values on
<col>or<th>elements). Content in subsequent rows does not affect column widths. This is faster because the browser can begin rendering as soon as it receives the first row.
table-layout: auto vs fixed
/* Auto layout (default) -- browser calculates widths from content */
table.auto-table {
table-layout: auto;
width: 100%;
}
/* Fixed layout -- widths set by first row or col elements */
table.fixed-table {
table-layout: fixed;
width: 100%;
}
/* With fixed layout, set explicit column widths */
table.fixed-table th:nth-child(1) { width: 30%; }
table.fixed-table th:nth-child(2) { width: 25%; }
table.fixed-table th:nth-child(3) { width: 25%; }
table.fixed-table th:nth-child(4) { width: 20%; }
/* Handle overflow in fixed layout cells */
table.fixed-table td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
With table-layout: fixed, content that is wider than the assigned column width will overflow the cell. This is why it is important to pair it with overflow: hidden and text-overflow: ellipsis to gracefully truncate long content. You can also use word-wrap: break-word if you want long text to wrap within the cell instead of being truncated.
Fixed Layout with Text Wrapping
table.fixed-wrap {
table-layout: fixed;
width: 100%;
}
table.fixed-wrap td {
word-wrap: break-word;
overflow-wrap: break-word;
hyphens: auto;
}
/* Alternatively, truncate with ellipsis */
table.fixed-truncate td {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 0; /* Forces ellipsis in fixed layout */
}
table-layout: fixed for large data tables with hundreds or thousands of rows. The performance improvement is noticeable because the browser does not need to measure every cell before rendering. This also gives you precise control over column widths, which is essential for consistent layouts.Vertical Alignment in Table Cells
The vertical-align property in table cells works differently than it does on inline elements. In a table cell context, it controls the vertical position of the cell content within the cell box. The available values are top, middle, bottom, and baseline.
vertical-align in Table Cells
/* Default: middle alignment */
td {
vertical-align: middle;
}
/* Top alignment -- useful for cells with varying content heights */
td {
vertical-align: top;
}
/* Bottom alignment */
td {
vertical-align: bottom;
}
/* Baseline alignment -- aligns text baselines across cells */
td {
vertical-align: baseline;
}
/* Practical example: mixed-height content */
table.mixed-content td {
vertical-align: top;
padding: 12px;
}
table.mixed-content td.avatar {
width: 60px;
vertical-align: middle;
}
table.mixed-content td.actions {
vertical-align: middle;
text-align: right;
}
The default vertical-align: middle works well for simple tables where all cells have similar content height. Switch to vertical-align: top when cells contain multi-line text or varying amounts of content, such as a product description column next to a short status column. This prevents odd-looking centered text in short cells when adjacent cells have much taller content.
The empty-cells Property
The empty-cells property controls whether borders and backgrounds are drawn around empty table cells (cells with no content whatsoever). This property only applies when border-collapse is set to separate.
empty-cells Property
/* Show borders and background on empty cells (default) */
table {
border-collapse: separate;
empty-cells: show;
}
/* Hide borders and background on empty cells */
table {
border-collapse: separate;
empty-cells: hide;
}
/* Practical example */
table.schedule {
border-collapse: separate;
border-spacing: 4px;
empty-cells: hide;
}
table.schedule td {
border: 1px solid #ddd;
padding: 10px;
background-color: #f9f9f9;
}
/* Empty cells will appear as gaps in the grid,
which is useful for schedule/calendar tables */
empty-cells property is only relevant when border-collapse: separate is set. When using border-collapse: collapse, empty cells always show their borders because the borders are shared with adjacent cells. A cell containing only whitespace is not considered empty -- it must be completely empty or contain only an HTML comment.Responsive Tables
Tables are inherently rigid -- they have fixed columns that must all fit within the viewport width. On small screens, wide tables overflow their containers and break the page layout. There are several strategies for making tables responsive, each with its own trade-offs.
Strategy 1: Horizontal Scroll
The simplest and most common approach is wrapping the table in a container with horizontal scrolling. This preserves the table layout exactly as designed and simply allows users to scroll sideways to see all columns.
Horizontal Scroll Wrapper
<div class="table-responsive">
<table>
<!-- table content -->
</table>
</div>
CSS for Horizontal Scroll
.table-responsive {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
}
/* Optional: scroll shadow indicators */
.table-responsive {
background:
linear-gradient(to right, white 30%, transparent),
linear-gradient(to left, white 30%, transparent),
linear-gradient(to right, rgba(0,0,0,0.1), transparent 15px),
linear-gradient(to left, rgba(0,0,0,0.1), transparent 15px);
background-position: left center, right center, left center, right center;
background-repeat: no-repeat;
background-size: 40px 100%, 40px 100%, 15px 100%, 15px 100%;
background-attachment: local, local, scroll, scroll;
}
/* Ensure the table does not shrink below its content width */
.table-responsive table {
min-width: 600px;
width: 100%;
}
Strategy 2: Stacked Layout on Mobile
For tables with fewer columns, you can transform the table into a stacked card layout on small screens. Each row becomes a card, and each cell is displayed as a label-value pair. This requires using CSS to override the display properties of table elements.
Stacked Table Layout
@media screen and (max-width: 768px) {
table.stackable thead {
display: none; /* Hide the header row */
}
table.stackable tbody tr {
display: block;
margin-bottom: 16px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 8px 0;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
table.stackable tbody td {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 16px;
border: none;
border-bottom: 1px solid #f0f0f0;
text-align: right;
}
table.stackable tbody td:last-child {
border-bottom: none;
}
/* Show labels using data attributes */
table.stackable tbody td::before {
content: attr(data-label);
font-weight: 600;
color: #555;
text-align: left;
flex-shrink: 0;
margin-right: 16px;
}
}
HTML with data-label Attributes
<table class="stackable">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Department</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Name">Alice Johnson</td>
<td data-label="Role">Developer</td>
<td data-label="Department">Engineering</td>
<td data-label="Salary">$95,000</td>
</tr>
</tbody>
</table>
Strategy 3: Priority Columns
For tables with many columns, you can hide less important columns on smaller screens and keep only the essential ones visible. This preserves the table format while reducing the width needed.
Priority Column Hiding
/* Mark columns with priority classes */
@media screen and (max-width: 992px) {
.col-priority-low {
display: none;
}
}
@media screen and (max-width: 768px) {
.col-priority-medium {
display: none;
}
}
@media screen and (max-width: 576px) {
.col-priority-high {
display: none;
}
/* Only essential columns remain visible */
}
data-label attributes to every <td> element. This can be tedious for server-rendered tables, but it is necessary because CSS cannot read the content of <th> elements in a different row. Consider using JavaScript or a server-side loop to automate adding these attributes.Building a Complete Data Table
Now let us combine everything we have learned into a fully styled, professional data table. This table includes collapsed borders, styled headers, striped rows, hover effects, sticky headers, proper alignment, and responsive behavior.
Complete Styled Data Table -- HTML
<div class="data-table-wrapper">
<table class="data-table">
<caption>Employee Directory -- Engineering Division</caption>
<colgroup>
<col style="width: 5%;">
<col style="width: 22%;">
<col style="width: 18%;">
<col style="width: 20%;">
<col style="width: 12%;">
<col style="width: 12%;">
<col style="width: 11%;">
</colgroup>
<thead>
<tr>
<th>#</th>
<th>Employee</th>
<th>Position</th>
<th>Email</th>
<th class="text-right">Salary</th>
<th class="text-center">Status</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice Johnson</td>
<td>Senior Developer</td>
<td>alice@company.com</td>
<td class="text-right">$120,000</td>
<td class="text-center">
<span class="badge badge--active">Active</span>
</td>
<td class="text-center">
<button class="btn-icon">Edit</button>
</td>
</tr>
<!-- more rows... -->
</tbody>
<tfoot>
<tr>
<td colspan="4">Total Employees: 12</td>
<td class="text-right">$1,140,000</td>
<td colspan="2"></td>
</tr>
</tfoot>
</table>
</div>
Complete Styled Data Table -- CSS
/* Table wrapper for responsive scrolling */
.data-table-wrapper {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Base table styles */
.data-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
line-height: 1.5;
background: #fff;
}
/* Caption */
.data-table caption {
caption-side: top;
text-align: left;
font-size: 18px;
font-weight: 700;
color: #2c3e50;
padding: 16px 20px;
background: #fff;
border-bottom: 2px solid #3498db;
}
/* Header */
.data-table thead th {
background: linear-gradient(135deg, #2c3e50, #34495e);
color: #fff;
font-weight: 600;
text-transform: uppercase;
font-size: 11px;
letter-spacing: 0.08em;
padding: 14px 16px;
text-align: left;
border: none;
position: sticky;
top: 0;
z-index: 10;
}
/* Body cells */
.data-table tbody td {
padding: 12px 16px;
border-bottom: 1px solid #eee;
color: #444;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Zebra striping */
.data-table tbody tr:nth-child(even) {
background-color: #fafbfc;
}
/* Hover effect */
.data-table tbody tr {
transition: background-color 0.15s ease;
}
.data-table tbody tr:nth-child(even):hover,
.data-table tbody tr:nth-child(odd):hover {
background-color: #e8f4fd;
}
/* Footer */
.data-table tfoot td {
padding: 14px 16px;
font-weight: 700;
color: #2c3e50;
background: #f1f3f5;
border-top: 2px solid #dee2e6;
}
/* Alignment utilities */
.data-table .text-right { text-align: right; }
.data-table .text-center { text-align: center; }
/* Numeric cells */
.data-table td.text-right {
font-variant-numeric: tabular-nums;
font-family: 'SF Mono', 'Fira Code', monospace;
}
/* Status badges */
.data-table .badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.data-table .badge--active {
background: #d4edda;
color: #155724;
}
.data-table .badge--inactive {
background: #f8d7da;
color: #721c24;
}
/* Action buttons */
.data-table .btn-icon {
padding: 4px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fff;
color: #555;
font-size: 12px;
cursor: pointer;
transition: all 0.15s ease;
}
.data-table .btn-icon:hover {
background: #3498db;
color: #fff;
border-color: #3498db;
}
/* Responsive: scroll on small screens */
@media screen and (max-width: 768px) {
.data-table {
min-width: 700px;
}
}
Sorting Indicators with CSS
Many data tables include sortable columns. While the actual sorting logic requires JavaScript, you can create sort indicator arrows purely with CSS. This gives users a visual cue that columns are sortable and shows the current sort direction.
CSS Sort Indicators
/* Sortable column header */
th.sortable {
cursor: pointer;
user-select: none;
position: relative;
padding-right: 24px;
}
th.sortable::after {
content: '';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 5px solid rgba(255, 255, 255, 0.3);
margin-top: -4px;
}
th.sortable::before {
content: '';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid rgba(255, 255, 255, 0.3);
margin-top: 4px;
}
/* Active ascending sort */
th.sort-asc::after {
border-bottom-color: #fff;
}
/* Active descending sort */
th.sort-desc::before {
border-top-color: #fff;
}
th.sortable:hover {
background-color: rgba(255, 255, 255, 0.1);
}
Styling Table Borders Creatively
While simple solid borders are the most common choice, CSS allows you to create many interesting border patterns for tables. You can use horizontal-only borders for a clean modern look, or add accent borders to highlight specific sections.
Horizontal Lines Only (Modern Clean Style)
table.clean {
border-collapse: collapse;
width: 100%;
}
table.clean th,
table.clean td {
border: none;
border-bottom: 1px solid #e0e0e0;
padding: 14px 16px;
}
table.clean thead th {
border-bottom: 2px solid #333;
font-weight: 700;
color: #222;
}
table.clean tbody tr:last-child td {
border-bottom: 2px solid #333;
}
Borderless Table with Row Separation
table.borderless {
border-collapse: collapse;
width: 100%;
}
table.borderless th,
table.borderless td {
border: none;
padding: 12px 16px;
}
table.borderless tbody tr {
border-bottom: 1px solid transparent;
background-image: linear-gradient(#e8e8e8, #e8e8e8);
background-size: calc(100% - 32px) 1px;
background-repeat: no-repeat;
background-position: center bottom;
}
Practice Exercise
Create a complete responsive product inventory table with the following requirements:
- Create an HTML table with columns: Product ID, Product Name, Category, Price, Stock, and Status.
- Add at least 8 rows of sample data with realistic product information.
- Use
border-collapse: collapseand style the header with a dark background and white text. - Add zebra striping to alternate rows using
:nth-child(even). - Add a hover effect that highlights the entire row with a light blue background.
- Right-align the Price and Stock columns and use
font-variant-numeric: tabular-nums. - Add colored status badges: green for "In Stock," orange for "Low Stock," and red for "Out of Stock."
- Use
table-layout: fixedwithtext-overflow: ellipsisfor long product names. - Wrap the table in a responsive scroll container that activates on screens below 768px.
- Add a
<caption>styled at the top of the table with the text "Product Inventory." - Include a
<tfoot>row showing the total number of products and average price. - Add sticky headers so the header row stays visible when scrolling.
This exercise combines every concept from this lesson. Focus on creating a table that is both visually polished and fully functional on all screen sizes.