Day 2 of Run Explorer: Why the Table Scrolls Sideways and the Page Does Not
At 390 pixels wide, the run table grew a horizontal scrollbar and the rest of the page stayed still. I deleted the rule that contained that scroll, and the whole page slid sideways. I also hid the keyboard focus ring, then put it back.
The short version
The six runs from Day 1 sit in a real HTML table. Each run id is a button. At a phone width the columns stay readable, and the table wrapper scrolls sideways so the page itself does not.
Day 2 added no feature and no commit. I read the table component and the stylesheet, narrowed the browser to 390 pixels, and broke two rules on purpose.
What the markup is doing
RunTable receives the runs and an onOpen function. The parent owns the data. The table displays it and reports which run was chosen.
The element is a table because the rows are the same kind of record: one run per row, the same columns every time.
The caption says "Application runs. Open a run to inspect its steps." It has the class sr-only, which clips it to a one-pixel box. The words stay in the page for assistive technology and stay out of the visual layout.
Each header cell is th with scope="col". That marks the cell as the name of a column, so a screen reader can announce the header with the value.
key={run.id} tells React which row is which. Later, when the rows are sorted, run-003 is still run-003 if it moves. The position on screen is not the row's identity.
The run id is a button. Clicking it calls onOpen with that id. Today, App answers with an alert that the detail panel comes later.
<button className="run-link" onClick={() => onOpen(run.id)}>
{run.id}
</button>A div with a click handler would not respond to Enter, and it would not show up as a button.
What I broke
Both rules were already in the starter. I took each one away, looked at the page, and put it back.
1. The scroll that stayed inside the table
The table has white-space: nowrap, so the columns refuse to squash into unreadable slivers. The wrapper has:
.table-scroll {
overflow-x: auto;
}At 390 pixels I saw a horizontal scrollbar on the table. The heading and the rest of the page stayed put.
I deleted overflow-x: auto and looked at the narrow window again. The scrollbar moved to the page. The whole layout, including the title, slid sideways, because the wide table was now pushing the document.
I put the rule back. The scroll returned to the table.
2. The focus ring
Tabbing to a run button showed a pink outline:
button:focus-visible,
input:focus-visible,
select:focus-visible,
a:focus-visible {
outline: 3px solid #ff80d0;
outline-offset: 3px;
}I deleted the outline lines and tabbed through the run buttons. Focus was still moving, and there was no visible sign of where it was. I restored the outline immediately. Someone using the keyboard needs that ring.
Why the columns do not shrink
A data table is a grid of relationships. Status has to stay lined up with the run it describes. Shrinking every column until the words stack would make those relationships harder to see. Letting the table scroll inside its own box keeps the columns intact and leaves the rest of the page alone.
box-sizing: border-box on every element means the width you set includes padding and border. main stops at 1260 pixels wide and stays centered. The header is a flex row, and under 760 pixels it stacks into a column. Those rules are the page frame. The table scroll is separate from them.
What I set up on Day 2
Nothing new landed in git. The table, the button, and both CSS rules were already in the Day 1 starter. The work was learning which line does which job, and proving it by taking the line away.
That is the useful record. If a later layout starts scrolling the whole page sideways, the first place I look is .table-scroll. If a keyboard user loses their place, the first place I look is the focus outline.
Frequently asked questions
Why is the run list a table?
The rows are comparable records with the same columns. Table markup carries that meaning. A stack of divs can be styled to look similar and still throws the meaning away.
Why does the table scroll sideways on a phone?
The columns use nowrap so they stay readable. overflow-x: auto on the wrapper lets that width scroll inside the table. The page around it stays still.
Why is each run id a button?
Opening a run is an action. A button is reachable with the keyboard and has a role that assistive technology already understands. The click handler reports the run id. The parent decides what opening means.
What does the row key do?
key={run.id} identifies the same logical row after the list is reordered. React uses it to match the row to its data. The row's index on screen is not a stable identity.
Next up
The filters. The search text I am still typing stays in the component. The filters I submit belong in the URL, so a refresh and the Back button can restore them. I will write that up once Back restores the table and not just the address bar.
If you want to follow along, the repository is public and every day lands as a commit.
Related: Day 1 of Run Explorer: Why a Missing File and a Quoted Six Fail in Different Places
