Tables with border-collapse: collapse are painted with a synthesized border grid derived from the first cell's border (build_table_context → draw_table_borders). Two bugs in that path make most collapsed tables render a thick dark grid that isn't in the document:
1. none/hidden border sides contribute their computed width
Stylo keeps the computed border-*-width even when the side's border-style is none — the used width is only zeroed at conversion time (stylo_taffy::convert::border does check style.none_or_hidden()). The table code reads the raw widths without that check:
Since the initial value of border-width is medium (3px), every collapsed table whose first cell has no border at all gets a phantom 3px grid — gutters between all rows/columns plus a 3px outer border:
<table style="border-collapse: collapse">
<tr><td>plain a</td><td>plain b</td></tr>
</table>
renders with a 3px dark grid around and between the cells; Chrome/Firefox/Servo render no borders at all.
2. Every grid line is painted with border-top-color (and outer edges ignore none)
draw_table_borders resolves one color — the first cell's border_top_color — and paints every horizontal and vertical line with it (there's a // TODO: support different colors for different borders): https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-paint/src/render/border.rs#L538-L543
An unset border-top-color resolves to currentColor, so the lines come out near-black on typical pages even when the borders that actually exist are a different color. Real-world case (Sentry's weekly-report email): bar-chart columns separated with border-right: 10px solid #fff on a white background — invisible in real clients, but blitz paints 10px near-black bars. The outer-edge checks also only skip BorderStyle::Hidden, so a plain borderless table still gets its outer border painted.
Suggested direction
Within the existing first-cell approximation (no per-edge conflict resolution):
- treat a side as zero-width unless its style is visible (share the
none_or_hidden semantics of stylo_taffy::convert::border), in both layout (gap/outer border) and paint;
- pick width and color per axis: vertical lines from the first cell's widest visible left/right side, horizontal lines from the adjacent rows'
border-top/border-bottom (the common tr { border-bottom: … } separator pattern) falling back to the first cell's top/bottom;
- outer edges: the wider of the table's own border and the grid line for that axis, with
hidden on the table side suppressing the edge (CSS 2.2 §17.6.2.1) and ties resolved cell over row over table.
We've implemented this in our fork and it fixes the above cases while keeping the approximation: fifteenlabs@f531a51 — happy to upstream it as a PR if the approach looks right.
Tables with
border-collapse: collapseare painted with a synthesized border grid derived from the first cell's border (build_table_context→draw_table_borders). Two bugs in that path make most collapsed tables render a thick dark grid that isn't in the document:1.
none/hiddenborder sides contribute their computed widthStylo keeps the computed
border-*-widtheven when the side'sborder-styleisnone— the used width is only zeroed at conversion time (stylo_taffy::convert::borderdoes checkstyle.none_or_hidden()). The table code reads the raw widths without that check:blitz-dom/src/layout/table.rs— gap forBorderCollapse::Collapseismax(border_left_width, border_right_width)×max(border_top_width, border_bottom_width)of the first cell, styles never consulted: https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-dom/src/layout/table.rs#L120-L134blitz-paint/src/render/border.rsdraw_table_borders— same for the painted width.Since the initial value of
border-widthismedium(3px), every collapsed table whose first cell has no border at all gets a phantom 3px grid — gutters between all rows/columns plus a 3px outer border:renders with a 3px dark grid around and between the cells; Chrome/Firefox/Servo render no borders at all.
2. Every grid line is painted with
border-top-color(and outer edges ignorenone)draw_table_bordersresolves one color — the first cell'sborder_top_color— and paints every horizontal and vertical line with it (there's a// TODO: support different colors for different borders): https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-paint/src/render/border.rs#L538-L543An unset
border-top-colorresolves tocurrentColor, so the lines come out near-black on typical pages even when the borders that actually exist are a different color. Real-world case (Sentry's weekly-report email): bar-chart columns separated withborder-right: 10px solid #fffon a white background — invisible in real clients, but blitz paints 10px near-black bars. The outer-edge checks also only skipBorderStyle::Hidden, so a plain borderless table still gets its outer border painted.Suggested direction
Within the existing first-cell approximation (no per-edge conflict resolution):
none_or_hiddensemantics ofstylo_taffy::convert::border), in both layout (gap/outer border) and paint;border-top/border-bottom(the commontr { border-bottom: … }separator pattern) falling back to the first cell's top/bottom;hiddenon the table side suppressing the edge (CSS 2.2 §17.6.2.1) and ties resolved cell over row over table.We've implemented this in our fork and it fixes the above cases while keeping the approximation: fifteenlabs@f531a51 — happy to upstream it as a PR if the approach looks right.