12/03/2017
When diving into web development, styling HTML tables with CSS can often feel like navigating a minefield. What seems straightforward for other HTML elements suddenly becomes a perplexing challenge with tables. You apply a border to a row, and it disappears. You set a percentage height, and nothing changes. And that crisp bottom border for your table? It ends up under every single cell! If you've encountered these frustrations, you're not alone. This article will demystify these common CSS table styling conundrums, explaining why they occur and, more importantly, how to fix them effectively.

- The Elusive Row Border: Why `display: table-row` Ignores Your Borders
- The Height Hurdle: Why Percentage Heights Fail on `table-row`
- The Table Border Blunder: Table vs. Cell Borders
- Comparative Solutions Table
- Frequently Asked Questions (FAQs)
- Q1: Can I use `outline` instead of `border` on `table-row`?
- Q2: Why does `border-collapse: collapse;` on the table make my cell borders look better?
- Q3: My table border looks weird, it's not a perfect rectangle. What could be wrong?
- Q4: Is it better to use CSS `display: table` properties or actual HTML `<table>` elements?
- Q5: How can I ensure my table fills the available width?
- Conclusion
The Elusive Row Border: Why `display: table-row` Ignores Your Borders
It's a common scenario: you want a border around each row in your table, so you target the `table-row` class (or the `tr` element) and apply a `border` property. To your dismay, nothing appears. The code looks correct, but the visual result is not what you expected. This behaviour stems from how the CSS box model interacts with table layout modules.
In the standard CSS box model, elements like `div`, `p`, or `span` (when set to `display: block` or `display: inline-block`) create a rectangular box around their content, padding, and border. However, `display: table-row` elements (and the native `tr` HTML element) behave differently. They are part of a special table rendering model designed to align content in a grid. In this model, properties like `border` and `padding` typically do not directly apply to the row itself in a visible, enclosing manner. Instead, these properties are primarily designed to be rendered on the table cells.
Think of a table row not as a standalone box, but as an invisible container that holds the cells together horizontally. The cells (`td` or `th`, or elements with `display: table-cell`) are the actual 'boxes' within the table structure that render content, padding, and borders. Therefore, when you apply `border: 1px solid black;` to a `div` with `display: table-row`, the browser essentially ignores it because a table row is not designed to render its own border in that fashion.
Solutions for Row Borders: Targeting the Cells
The most straightforward and widely supported way to achieve row borders is to apply them to the individual cells within that row. You can then manage their appearance using the `border-collapse` property on the parent table.
Method 1: Borders on Cells (Most Common)
Instead of trying to border the row, border the cells. If you want a line between rows, apply a `border-bottom` to the cells in all rows except the last one, or a `border-top` to all cells except the first row.
.tablecell { display: table-cell; border: 1px solid black; /* This will create borders around each cell */ padding: 8px; } .table { display: table; border-collapse: collapse; /* Essential for merging cell borders nicely */ width: 100%; } .tablerow { display: table-row; } With `border-collapse: collapse;`, adjacent cell borders will merge into a single, clean line, giving the appearance of borders around rows and columns.
Method 2: Specific Row Separators
If you only want a horizontal line *between* rows, you can apply a `border-bottom` to the cells of all but the last row, or `border-top` to all but the first row.
.tablerow:not(:last-child) .tablecell { border-bottom: 1px solid black; } .table { display: table; border-collapse: collapse; width: 100%; } /* Ensure vertical borders if needed */ .tablecell { border-left: 1px solid black; border-right: 1px solid black; padding: 8px; } This approach gives you fine-grained control over where the borders appear. Remember, the key is to apply the border property to the `table-cell` elements, not the `table-row`.
Understanding `position: absolute` and Borders
You mentioned that `position: absolute;` on `.tablerow` makes the border appear but causes rows to overlap. This is because `position: absolute` takes the element out of the normal document flow. When an element is absolutely positioned, it no longer participates in the table layout algorithm. It becomes a free-floating box, and as a regular box, it can now render its borders. However, this completely breaks the tabular structure, which is why your rows overlap. It's not a viable solution for maintaining a table layout.
The Height Hurdle: Why Percentage Heights Fail on `table-row`
Another common head-scratcher is setting a percentage height on a table row (e.g., `height: 40%`) only to find it has no effect, while pixel values (`height: 40px`) work perfectly. This behaviour is standard CSS and isn't unique to table rows, but it's particularly noticeable here.
For a percentage height to work on an element, its parent element must have a defined height. If the parent's height is `auto` (which is the default for most elements, meaning it will expand to fit its content), then a percentage of `auto` is undefined, and the percentage height on the child will effectively resolve to `auto` as well.
In the context of your `display: table-row` elements, their parent is the `div` with `display: table` (or the native `table` element). If your `.table` element doesn't have an explicit height set, then its children (the rows) cannot calculate a percentage of an undefined height. This is why `height: 40%` fails, but `height: 40px` (a fixed value) works, as it doesn't rely on the parent's height.
Solutions for Row Heights: Defining Parent Height or Using Fixed Values
Method 1: Define Parent Height
If you want rows to take up a certain percentage of the table's height, you must define the height of the table itself.
.table { display: table; height: 300px; /* Define a fixed height for the table */ width: 100%; } .tablerow { display: table-row; height: 50%; /* Now this will work, taking 50% of 300px */ } .tablecell { display: table-cell; vertical-align: top; } In this example, each `tablerow` would take up 50% of the 300px height, resulting in rows that are 150px tall. If you have more than two rows, they would equally divide the remaining height, or you'd need to adjust percentages accordingly (e.g., `height: 33.33%` for three rows).
Method 2: Use Fixed Pixel Heights
If you don't want to constrain the overall table height, or if percentage distribution isn't critical, simply use fixed pixel values for your row heights. This is often the most reliable approach for consistent row sizing.

.tablerow { display: table-row; height: 60px; /* This will always work, regardless of parent height */ } .tablecell { display: table-cell; vertical-align: middle; } The Table Border Blunder: Table vs. Cell Borders
You wanted a solid black line at the bottom of your entire table, but trying `table style="border-bottom-color:#000000"` didn't work, and `.bottom td {border-bottom: 2px solid black !important;}` put lines everywhere. This is another classic distinction in CSS table styling.
Issue 1: `border-bottom-color` Alone
When you use `border-bottom-color:#000000`, you are only specifying the colour of the border. For a border to be visible, it also needs a `border-width` and a `border-style`. Without these, the border defaults to `0` width and `none` style, making it invisible regardless of the colour. This is why it had no effect.
Issue 2: `td {border-bottom: ...}`
Applying `border-bottom` to `td` elements (or your `.tablecell` class) means that *every single data cell* in your table will have a bottom border. This creates a grid-like appearance with lines under all content, which is not what you wanted for a single table-wide bottom border.
Solutions for a Table-Wide Border
Method 1: Apply Border Directly to the Table
The most direct way to get a border on the entire table element is to apply the `border` property directly to the `table` (or your `div` with `display: table`).
.table { display: table; width: 100%; border-collapse: collapse; /* Important if you have cell borders too */ border-bottom: 2px solid black; /* This applies the border to the entire table wrapper */ } .tablecell { display: table-cell; padding: 8px; /* If you also have cell borders, ensure they don't interfere */ /* For example, if you want only the table border, remove cell borders */ /* border: none; */ } This will draw a border around the entire outer perimeter of the table. If you only want a bottom border, use `border-bottom: 2px solid black;` as shown.
Method 2: Using `border-spacing` for Visual Separation (Less Common for Single Border)
While not directly for a single bottom border, it's worth noting `border-spacing` (applied to the table) can create gaps between cells, which can sometimes be styled to look like borders, but it's more for internal grid lines. It works when `border-collapse` is set to `separate`.
.table { display: table; width: 100%; border-spacing: 0 5px; /* Creates 5px vertical spacing between rows */ border-bottom: 2px solid black; /* Still need this for the table's own border */ } .tablerow { display: table-row; } .tablecell { display: table-cell; background-color: lightgrey; /* To make the spacing visible */ padding: 8px; } This method is more complex for a simple bottom border and typically isn't the primary solution for your specific problem, but it illustrates how table rendering can be manipulated.
Comparative Solutions Table
| Desired Effect | Common Mistake | Correct CSS/HTML | Explanation |
|---|---|---|---|
| Border around each table row | `.tablerow { border: 1px solid black; }` | `.tablecell { border: 1px solid black; }` `<parent_table> { border-collapse: collapse; }` | Rows don't render borders; cells do. `border-collapse` merges cell borders. |
| Set row height by percentage | `.tablerow { height: 50%; }` (without parent height) | `.table { height: 300px; }` `.tablerow { height: 50%; }` | Percentage heights require a defined height on the parent element. |
| Single bottom border for the entire table | `<table style="border-bottom-color:#000;">` or `.tablecell { border-bottom: 2px solid black; }` | `.table { border-bottom: 2px solid black; }` | `border-bottom-color` needs width/style. Applying to `td` borders individual cells. Apply to the table itself. |
Frequently Asked Questions (FAQs)
Q1: Can I use `outline` instead of `border` on `table-row`?
A: While `outline` can appear on elements where `border` might not (as it doesn't take up space in the layout), its behaviour on `display: table-row` can still be inconsistent across browsers and might not provide the crisp, contained line you're looking for within the table's structured layout. It's generally not recommended for creating robust table grid lines.
Q2: Why does `border-collapse: collapse;` on the table make my cell borders look better?
A: When `border-collapse` is set to `separate` (the default), each cell maintains its own borders, and there's a small `border-spacing` between them. When set to `collapse`, adjacent cell borders are merged into a single border. This creates a cleaner, more unified grid appearance, where lines between cells and rows appear as continuous, single lines rather than double lines with gaps.
Q3: My table border looks weird, it's not a perfect rectangle. What could be wrong?
A: This can happen if you have padding or margins on the table itself, or if you're mixing `border-collapse: collapse;` with `border-spacing`. Ensure your table's `border-collapse` property is consistent with your border strategy. Also, check for any rogue `padding` or `margin` on the table or its direct children that might push the border out of alignment.
Q4: Is it better to use CSS `display: table` properties or actual HTML `<table>` elements?
A: For truly tabular data (data that makes sense in rows and columns, like a spreadsheet), always use the semantic HTML `<table>`, `<tr>`, `<td>`, and `<th>` elements. They provide better accessibility, are more robust, and are understood by screen readers and other assistive technologies. Using `display: table` on `div` elements is a CSS layout technique that can mimic table behaviour for non-tabular content, but it lacks the semantic meaning of proper HTML tables. Stick to semantic HTML tables for data tables.
Q5: How can I ensure my table fills the available width?
A: Apply `width: 100%;` to your `display: table` element (or the native `<table>` element). This will make the table expand to fill the width of its parent container. You can then control column widths using percentages or fixed pixel values on the `table-cell` elements (or `td`/`th`).
Conclusion
Styling tables in CSS can indeed be a source of frustration, but understanding the underlying rendering model for table elements is key to mastering them. The main takeaways are clear: borders primarily belong on table cells, not rows, and percentage heights on rows require a defined height on the parent table. Furthermore, always ensure you provide all three border properties (width, style, and colour) when defining a border, and apply table-wide borders directly to the `<table>` element itself, not its individual cells.
By applying these principles and understanding why certain properties behave as they do within the table layout model, you'll be well-equipped to create clean, well-structured, and visually appealing tables for your web projects. Keep experimenting, and remember that consistent application of CSS properties is paramount for achieving the desired results.
If you want to read more articles similar to Unravelling CSS Table Styling Mysteries, you can visit the Automotive category.
