Build1 publisher3 min readPublished
Omitting one spec div hands Product C's IP68 rating to Product B
A CSS grid places the children it is handed and counts nothing else, so a conditional that skips an unrated value slides the next product's number under the wrong header. The same omission in a real table row does the same thing.
The Engineer · Build desk

What happened
- A dev.to post walks a three-product comparison table built as a CSS grid, where each .spec-row declares four 1fr columns and the values are plain divs with nothing declaring which column they belong to.
- Rebuilding the row as a <tr> with scope headers does not stop it: omit the second <td> and IP68 moves into Product B's column, unannounced on screen and in the accessibility tree.
- The post's fix is representing the absent value explicitly in whichever structure is used, an empty <td> or a real placeholder such as "Not rated".
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Everything downstream that resolves columns by position now publishes a specification a product does not have, and a wrong value is harder to catch than a missing one because nothing in the output marks it as suspect.
- constraint Semantic markup cannot be the checkpoint, because header association is computed by counting cells in document order, so correctness has to be enforced where the value is decided, in the template.
- decision Conditionally rendering a cell stops being a layout preference and becomes a content question: each row needs a stated meaning for absent data, and somebody has to own that wording.
- capability With the empty cell present, a screen reader or a scraper can compute the product from declared header association instead of inferring it from whatever sits next to the value.
`.spec-row` declares four tracks, `grid-template-columns: 1fr 1fr 1fr 1fr` [2], so the row has four slots whether or not a child exists for each one. The water-resistance row hands it three children: the label, Product A's IP67, and Product C's IP68 [3]. Three children in four slots leaves the last slot unoccupied, under Product C's header [16]. The conditional written to avoid a hollow box in Product B's column [4] produced one in Product C's.
A reviewer scanning the rendered page for missing data does find a gap, and it sits in the last column [16]. Product C is the product that has the rating; Product B's column is the one displaying IP68 [5]. Whether that empty slot is visible at all depends on how the cells are painted, and the rule quoted in the post sets track sizes and nothing else [2].
The extraction script never looks at the layout. It walks `.spec-value` by index inside the row and maps position 0 to Product A, 1 to Product B, 2 to Product C [6]. Position 1 holds "IP68", so the script records a water-resistance rating for a product that has none [7]. An assistant handed that output can answer a question about Product C with Product B's value. According to the post the model is not the component that failed: the data it received had already attached the value to the wrong product [8].
The accessibility path fails the same way. The divs declare no table semantics and no column relationships. A screen reader may expose "Water resistance", "IP67" and "IP68" as a sequence of content, and the structure never says which product each value belongs to [9].
Rebuild the row as `<tr>` with `scope="row"` on the header cell and `scope="col"` on the product headers. Each data cell gets a declared position, the intersection of one row header and one column header [10]. The browser finds that intersection by walking cells in document order and counting [11]. Leave the second `<td>` out and IP68 moves into Product B's column exactly as it did in the grid. Neither the screen nor the accessibility tree announces that a cell is missing [12]. The post says: "scope computes a relationship. It doesn't verify that the cell claiming a position actually belongs there." [13]
This reaches your data when a template omits a child instead of emptying it and a consumer resolves columns by position [17]. The post presents hand-written markup as an example of Thursday's argument that a document's relationships have to be declared once, in structure, or every consumer reconstructs them separately [20]. It does not describe a production incident. The damage scales rightward. With three products and the second value skipped, one cell is misattributed, and each further product to the right of the omission takes its left neighbour's value [18].
The fix in the post is representing the missing value explicitly, an empty `<td>` or a real placeholder, in whichever structure is already in use. A grid with a properly rendered empty div in that slot stops the same shift [14]. The `<table>` element is not the fix. Representing that value turns the conditional into a data decision: somebody has to state what an absent value means for that row, and the post's preferred answer is "Not rated" [14].
What to watch
- Whether extraction tooling for comparison tables moves off child-index addressing to header-based lookup.
- Whether accessibility checkers start flagging rows whose cell count differs from the declared column count.
- Whether component libraries lint conditional children inside fixed-column grid rows.