Menu


Table


Rows of data under headings that sort, filter and explain, with rows that open, pin or group, optional selection and a foot for the pagination.

Customize the preview


Choices
Show
Flags
View
Projects
ID
Website redesignPRJ-1041Maya Chen€48,00024Design
Mobile appPRJ-1042Tom Reyes€120,00036Build
Brand refreshPRJ-1043Ada Okafor€32,50012Review
Help centerPRJ-1044Sam Kim€18,00058Live
Checkout flowPRJ-1045Lena Brandt€64,0009Planning

Options


Table options
ChoiceValuesDefaultWhat it does
looksmbk-table--look-<value>framedflatfilledframed
Framed: a bordered, rounded frame, the heading row on its own surface, room at the sides. Flat, for a small table inside a card: no frame, no heading surface, a rule over every row, no room at the sides, so the table's words line up with the card's other lines, and rows one line of text high. A flat table takes none of sticky-header, page-sticky-header and pin-first. Filled, for a calmer table on the page: the framed frame and rows, the heading row and the foot on a neutral fill, group rows sunken and low, no lines between headings, the same room at a row's ends and between its columns, and the arrow of a column not sorted only on hover and keyboard focus.
sticky-headersmbk-table--sticky-headerOn or offOff
A long table in a frame of its own: the heading row stays at the frame's top and the foot at its foot while the rows scroll inside it. The consumer limits the frame's height; the frame is then a named region the keyboard can reach. Framed and filled looks.
page-sticky-headersmbk-table--page-sticky-headerOn or offOff
A long table in the page: the frame keeps no height of its own, and the heading row stays in view under the room the page keeps clear at its top while the page scrolls, with the foot at the foot of the window, until the table ends. The table still scrolls sideways, its heading with it. Framed and filled looks.
pin-firstsmbk-table--pin-firstOn or offOff
A wide table: the first column stays in view while the table scrolls sideways, so every row keeps its subject. For a table on the page canvas, in the framed and filled looks.
align-topsmbk-table--align-topOn or offOff
Cells of very different heights: every cell starts at the top of its row, and its first line sits level with the first line of the others, so the eye reads straight across a tall row. All three looks.

Partssmbk-table__tablesmbk-table__captionsmbk-table__columnsmbk-table__header-rowsmbk-table__headersmbk-table__header-cellsmbk-table__header-textsmbk-table__header-labelsmbk-table__header-blocksmbk-table__header-iconsmbk-table__sortsmbk-table__filtersmbk-table__filter-countsmbk-table__infosmbk-table__rowsmbk-table__row-presssmbk-table__cellsmbk-table__startsmbk-table__leadsmbk-table__expandersmbk-table__pinsmbk-table__contentsmbk-table__choose-spacesmbk-table__valuesmbk-table__value-iconsmbk-table__stacksmbk-table__descriptionsmbk-table__copysmbk-table__prosesmbk-table__figuresmbk-table__barsmbk-table__groupsmbk-table__group-labelsmbk-table__group-countsmbk-table__detail-rowsmbk-table__detailsmbk-table__detail-titlesmbk-table__footersmbk-table__scroll-hintsmbk-table__scroll-tracksmbk-table__scroll-thumb

Examples


Usage

Sortable headings, six kinds of cell, and the foot with a range on the left and pages on the right.

Projects, usage
ID
Website redesignPRJ-1041Maya Chen€48,00024Design
Mobile appPRJ-1042Tom Reyes€120,00036Build
Brand refreshPRJ-1043Ada Okafor€32,50012Review
Help centerPRJ-1044Sam Kim€18,00058Live
Checkout flowPRJ-1045Lena Brandt€64,0009Planning

Flat, in a card

The flat look, for a small table inside a card: no frame and no heading surface, a rule over every row, its words lined up with the card's title, and rows one line of text high.

Best sellers this week

Best sellers, flat, in a card
ProductSoldTakings
Notebooks120€1,440
Fountain pens34€1,190
Desk lamps18€1,080
Gift cards52€1,040
Total224€4,750

Filled

The filled look, for a calmer table on the page: the heading row and the foot on a neutral fill, sunken group rows, no lines between headings. Only the sorted column shows its arrow; point at another heading, or Tab to it, and its arrow shows.

Projects, filled
Stage
Running2
Mobile appTom Reyes€120,000Build
Help centerSam Kim€18,000Live
Waiting on you3
Website redesignMaya Chen€48,000Design
Brand refreshAda Okafor€32,500Review
Checkout flowLena Brandt€64,000Planning

Filled, a line in the foot

A foot holding a line of words is one small control high; the paginations of the example above make it taller.

Projects, filled, a line in the foot
Stage
Running2
Mobile appTom Reyes€120,000Build
Help centerSam Kim€18,000Live

Filled, every kind of row

Column widths, a prose column, checkboxes, a pinned row, a row that opens with its detail, and the current row, all under group rows.

Changes, filled, every kind of row
Latest change
Changed this week2
Website redesign
Moved the opening hours onto the contact page, so the shop and the website always say the same thing.
€48,000
Mobile app
Sign-in now remembers the last account used on this phone.
€120,000

Next step

The sign-in change reaches the first hundred testers on Tuesday.
Changed earlier1
Help center
Split the long billing article into three short ones, each answering one question, and linked them from the invoice email.
€18,000

Filled, long table

Sixteen rows under group rows in a frame lower than the table: the heading row and the foot keep their look with the rows passing under them.

Projects, filled, long table
Stage
Running8
Website redesignMaya Chen€48,000Design
Mobile appTom Reyes€120,000Build
Brand refreshAda Okafor€32,500Review
Help centerSam Kim€18,000Live
Checkout flowLena Brandt€64,000Planning
Design tokensMaya Chen€22,000Build
Onboarding emailsSam Kim€9,500Live
Pricing pageTom Reyes€12,000Review
Waiting on you8
Partner portalLena Brandt€88,000Planning
Annual reportAda Okafor€27,000Design
Search upgradeSam Kim€54,000Build
Careers siteMaya Chen€16,500Live
Status pageTom Reyes€7,000Review
Docs migrationLena Brandt€41,000Build
Store locatorAda Okafor€19,000Planning
NewsletterSam Kim€4,500Live

Guidelines


Purpose

Rows of the same kind of thing, one per line, under headings the reader can sort by: clusters, users, orders. It is for comparing and finding, not for laying out a page. A card grid is for things that each need room; a table is for many things that each need a line.

When to use

  • Lists of records with several attributes each, where the reader scans and compares.
  • Anything the reader may want to sort or select in bulk.
  • A few figures inside a card, such as what was sold this week and the total: give the table the flat look, so it reads as part of the card rather than a second card inside it.
  • A table on the page whose heading should stay quiet, such as a dense list under group rows: give it the filled look, so fills mark the heading, the groups and the foot, and the rows carry the only lines.
  • A row whose detail is about one of its values, such as a part's count of the models it fits: open the detail from that value, a button in its cell (detailTrigger="cell"), rather than from a caret at the row's start.
  • A table longer than the screen, read down the page, such as every product in a catalog: give it the long form in the page (page-sticky-header), so the page keeps one scroll and the headings stay in view.
  • A long table in a panel of a set height, such as one tile of a dashboard: give it the long form in a frame of its own (sticky-header) and limit the frame's height.
  • A list of things to open, such as invoices, where a reader expects a press anywhere on a row to open one: give each row an href, or an onClick for something that opens in the page.
  • A small card about a row, such as a person's or a ship's, that a press on the row opens beside it: hang a popover on the row's own button (openerId), so the row stays one Tab stop.
  • A table whose rows change while the reader compares them, through a filter, pages, a search or tabs: give its columns widths, so they stay where they are.
  • A few records compared side by side, each heading a column of its own with its name, its facts and its commands: give those columns a tall heading (tallHeader), so the head of each record sits above its figures.
  • A table of many short values in a comfortable page, such as a long list of figures: give it density="compact", so it sits tighter than the page around it.
  • Cells of very different heights, such as a first column that wraps over a line and a button beside figures of one line: give the table align-top, so each figure stays level with the name it belongs to.

When not to use

  • Fewer than three columns. A list will do.
  • Content that needs room or pictures. Use cards. A column of short sentences is fine as a prose column.
  • A narrow screen. Tables do not reflow; give the reader a list there.
  • A press on the whole row, in a table people copy words out of. A press on a row's words presses the row, so the pointer cannot pick them; put a link on the row's name instead.
  • A long or wide table in the flat look. The flat look takes none of the long and pinned forms; give such a table the framed or filled look.
  • The compact table where a reader presses something in most rows on a touch screen. A press area stops at its own row, so a lower row gives a finger less to aim at.
  • The long form in the page inside a part that scrolls sideways or clips what runs past it. The heading row follows the nearest part around the table that scrolls or clips, so there it follows that part rather than the page.

Usage rules

Do

  • Put the identifying column first.
  • Keep a value to one line; put the rest in the description, and give sentences a prose column.
  • Line figures up at the end of their column.
  • Name each row with label when it has a caret, a detail or a checkbox, so their names say which row they act on.
  • Open a row's detail from the value it is about, a button in that value's cell, and keep the caret for a detail about the whole row.
  • Give a table longer than the screen the long form in the page, so its headings stay in view and the page keeps one scroll.
  • Under a bar fixed at the top of the page, set the page's scroll padding at the top to the room the bar covers, so the heading row stays under the bar. The system's app bar that hides on scroll sets it for you.
  • Pin the first column of a table wider than its place, on the page canvas.
  • In markup written by hand, give a chosen row the row's selected modifier as well as ticking its checkbox.
  • Mark the row a button in it acted on as current, and keep the chosen fill for rows ticked with their checkboxes.
  • Mark a row that can be pressed as current while what it opened is open, so the reader sees where they came from.
  • Hang a popover a row opens on the row's own link or button, and keep it in one of that row's cells, so Tab goes on through the panel from the row.
  • Give a row an href when its press goes to an address, so it stays a real link that opens in a new tab.
  • Give a small table inside a card the flat look, so its words line up with the card's other lines.
  • Keep a filled table that sticks or holds a column on the page canvas, the floor its heading and foot are laid over.
  • Give the columns of a table whose rows change a width each, and let the column that names the row take the rest.
  • Give a table with column widths a least width that holds its columns on a phone, or none when it fits its frame anyway.
  • Keep a tall heading to a few lines in a long table, so it stays in view while the rows scroll under it.
  • Set a table's own density with density, on the table, and leave the rest of the page to the page's.
  • Give a table align-top when its cells differ a lot in height, so each first line stands level with the name beside it.
  • In a table with align-top, put a control, a tag or a picture in TableContent, which centers it on the first line.

Don't

  • Sort on the client for more rows than fit in memory.
  • Use the row hover as the only sign of interactivity.
  • Let a highlight carry meaning by its tint alone.
  • Tell the chosen rows from the current row by their fill. Both take the same fill, so the checkboxes and the current row's words tell them apart.
  • Put a long explanation in a heading's info: it is for a sentence or two.
  • Open one detail from two controls, a caret and a button in a cell: choose one, and give the table detailTrigger="cell" for the button.
  • Open a row's detail with a press on the whole row. The row's button cannot say whether the detail is open: use the caret, or a button in a cell.
  • Put a button in a row that does what a press on the row does. It is a second Tab stop saying the same thing.
  • Put a framed table inside a small card: its frame reads as a second card, and its words sit in from the card's own.
  • Measure the rows to work out a column's width. The widths then follow the rows, which is what a set width is for stopping.
  • Give a column less room than a tag, a button or a picture in it takes. Words wrap, those do not, and they run over the next column.
  • Rely on an arrow to tell a reader a filled table's heading sorts: it stays unseen until they go to it, and on a touch screen until they press. Where sorting matters, say so near the table or sort a column from the start.
  • Let a pinned first column take the rest in a table that scrolls sideways on a phone. Held at that width, it leaves the other columns little room to pass: give it a share or a size token.
  • Put long sentences in a tall heading. It is the head of a column, and the rows carry the detail.
  • Put data-density on a part of a page other than a table's frame. A table is the one part that takes a density of its own.
  • Give a long table in the page a frame of its own lower than the window. Two scrolls on one page take the wheel from the page wherever the pointer is over the table.
Projects, name first
Website redesignMaya Chen€48,000
Mobile appTom Reyes€120,000
Brand refreshAda Okafor€32,500
Do

Put the column that names the row first, so the reader can find a row by scanning down the left.

Projects, name in the middle
€48,00024Website redesignMaya Chen
€120,00036Mobile appTom Reyes
€32,50012Brand refreshAda Okafor
Don't

With the name buried in the middle, the reader has to read across every row to find one.

Projects, sentences in a prose column
ProjectNote
Website redesign
Started on 12 March by the brand team and due at the end of April.
Do

Keep a value to one line, and give sentences a prose column, which wraps within a readable measure.

Projects, a sentence in a value
ProjectOwner
Website redesign, started on 12 March by the brand team and due at the end of AprilMaya Chen
Don't

A sentence in a value column stays on one line, stretches the table and pushes the other columns out of view.

Projects, figures at the end
ProjectBudget
Website redesign€48,000
Mobile app€120,000
Brand refresh€32,500
Do

Line figures up at the end of their column, so they compare digit by digit.

Projects, figures at the start
ProjectBudget
Website redesign€48,000
Mobile app€120,000
Brand refresh€32,500
Don't

Figures at the start of their column put hundreds under thousands.

Best sellers this week

Best sellers, flat inside a card
ProductSoldTakings
Notebooks120€1,440
Fountain pens34€1,190
Desk lamps18€1,080
Do

Inside a card, give a small table the flat look, so its words line up with the card's own and it reads as part of the card.

Best sellers this week

Best sellers, framed inside a card
ProductSoldTakings
Notebooks120€1,440
Fountain pens34€1,190
Desk lamps18€1,080
Don't

A framed table inside a small card draws a second card inside the first, its words set in from the card's own.

Anatomy

  • The block, a frame around a real table element, with a hidden caption naming it. It comes in three looks: framed, the default, a bordered and rounded frame for a table on the page; flat, for a small table inside a card; and filled, the framed table with a calmer heading (below). The parts here are the framed look's.
  • The header row on the default surface, with a rule under it in the control color. Each header part holds a heading in the small label style and, when sortable, an arrow: up on a column sorted ascending, down on one sorted descending, both ways on a column not sorted; hairlines separate the headings. The first heading may carry the choose-all checkbox. After the heading may come a filter button, a funnel with a count beside it while filters apply, and an info button; both are the extra-small quiet icon button.
  • A column of figures lines up at the end: its heading, its arrow (before the label, so the label meets the figures' edge) and its cells.
  • The row part, one per record, with a subtle rule under it, taking the hover fill under the pointer. A chosen row takes its own fill, beside its ticked checkbox, and keeps it under the pointer, and its descriptions turn one step darker so they stay readable on it. A disabled row is dimmed.
  • The cell part, holding a value part: text with an optional icon before it, a description straight under it, both in the tiny style, and a copy button after it. A cell may instead hold an avatar beside a link, a tag, a badge, a row of icon buttons, a picture, or a figure with a bar after it, the progress track filled to its share; the content part lines them up. Numbers take digits of equal width, so they line up down a column.
  • A prose column holds sentences: its cells wrap between a narrowest and a widest measure, and the other cells of the row line up with the first line.
  • A table whose cells differ a lot in height may start every cell at the top of its row, each first line level with the others.
  • A column may keep a width of its own, and the table then keeps every column where it is while its rows change. The column parts, one per column at the table's top, hold those widths.
  • A column may be headed by a block rather than a line: a few lines and buttons, such as a record's name, a line of facts and its commands, in the header block part. Its heading grows to hold them, and the heading row is as high as its tallest heading, the rule running under that one. Every heading in that row starts at the top, and a column that did not ask keeps its one line.
  • A row that can be pressed holds one link or button, the row press part, with no words to show, lying over the whole row; the controls in its cells lie over that. It looks like any other row: the hover fill under the pointer, and the chosen row's fill while it is pressed and while what it opened is open.
  • Row states beside chosen and disabled: the current row takes the chosen row's fill and keeps it under the pointer, in a table with or without checkboxes; a pinned row has a brand edge on its start side and a pin at its start; a highlighted row takes the info tint; a rule above a row, stronger than the rest, starts a new group.
  • A row that opens has a caret at its start, pointing to the end while closed and down while open, and unfolds a detail row under it: one cell across every column on the sunken surface, often a title over a few lines. In a table with such rows, or pinned ones, every first cell keeps the same room at its start, so the columns line up. A row may instead open from a button of the page's own in one of its cells, such as a count that opens the list it counts; the table then draws no caret, and no room at the rows' start unless a row is pinned. An open row draws no rule under it, so its detail joins it; the detail keeps the rule under it.
  • A group heading row: one cell across the row with a label and a count in the subtle color, starting the rows under it.
  • The choose checkbox, when rows are chosen: a subtle small checkbox in the first column, and the same in the first heading for every row.
  • The footer part, under a strong rule, for the range pagination on the left, with its small select, and the numbers pagination on the right.
  • Three forms for big tables, in the framed and filled looks. The long form keeps a long table's heading row in view, in one of two places. In a frame of its own (sticky-header), whose height the page limits, the heading row stays at the frame's top and the foot at its foot while the rows scroll inside it. In the page (page-sticky-header), the frame keeps no height of its own: while the page scrolls, the heading row stays at the top of what shows and the foot at the window's foot, until the table ends. The pinned form (pin-first) keeps the first column in view while a wide table scrolls sideways. Each heading that sticks or moves draws its own rules and surface, so they stay with it and nothing passing behind shows at its edges.
  • The flat look draws the same parts with less around them. It has no frame and no rounded corner, no fill on the heading row and no hairlines between its headings, which are in the small body style. A subtle rule runs over every row, the heading row's included, and the table's first and last cells keep no room at the sides, so the table's words line up with the card's own. Each row is one line of the body text with a small inset above and below, and the heading row is as high as the others; a row grows only where a cell holds something taller than a line, such as a button or a value over its description.
  • The filled look keeps the framed table's frame, its rows and their states, and marks its other parts with fills rather than lines. The heading row and the foot sit on a neutral fill, each with a subtle rule along it, and no hairlines separate the headings. A group row is sunken and one small control high, and a foot is at least that high, growing with what it holds. The room at a row's ends and between its columns is the same. A column not sorted shows its arrow only while the pointer is over its heading or the keyboard is on its sort button.

Behavior

A row is at least one medium control high, and grows with what its cells hold: a value over its description keeps a medium inset above and below it, so its two lines never run up to the rules, and a prose cell's sentences grow the row line by line.

When one cell of a row is much taller than the others, the others sit in the middle of that height, a line or two below the name they belong to, and the eye has to match them up row by row. With align-top every cell starts at the top of its row and its first line stands where a one-line value's does, so the eye reads straight across. That holds for plain words, a value, a value over its description, a link or value that wraps and a block of the page's own, and a row's caret, pin and choose box and a value's copy button stay with the first line.

A row of one line looks the same either way, so the flag changes only the rows that are taller. Every look takes it, a held first column on a phone included. A prose column already lines its own row up at the top, and the flag does the same for a table with no column of sentences, without the prose measure. In the flat look a row is one line of text, so a tag, an avatar or a button, taller than that line, starts at the top of the row with its middle a little below the line. Markup written by hand takes the block's align-top modifier and needs no script.

Pressing a heading's filter button opens a list of choices that tick; ticking one leaves the list open, and the consumer filters the rows and passes the new count. Pressing the info button opens its explanation in a popover beside it; Escape, a press outside or moving focus away closes it. Pressing a row's caret opens or closes its detail row; the table keeps which rows are open, or the consumer does when it passes them in. With detailTrigger="cell" the table draws no caret: a button the consumer puts in one of the row's cells opens the detail, and the consumer holds which rows are open in expanded. Give that table an id; tableDetailId(id, row.id) is the id of each row's detail row, for the button's aria-controls. Pressing a sortable heading asks the consumer to sort by it; the sorted heading reads in the default text color, shows its direction with its arrow and announces it. The sort button and the copy button each give the pointer at least 24 by 24 px to press. Choosing rows is the consumer's to handle; the table draws the checkboxes and the chosen rows' fill and reports the presses. In a selectable table a row can be left out of the choosing (selectable: false): it has no checkbox and keeps the checkbox's room empty, so its first value lines up with the others, and the heading's checkbox speaks for the rows that can be chosen, ticked when every one of them is; pressing it asks the consumer to choose, or let go of, those rows only. A row also passes on any data- attribute given to it, such as 'data-task': '42', to its row element, for the page's own scripts, checks and links. A row can also be the current one (current), in a table with checkboxes or without. It is the one row that a button of the page's own in it acted on, such as the part fitted or the plan in use. It takes the chosen row's fill and darker descriptions, and its row element has aria-current="true" for screen readers. Use selected for the rows a reader chooses with the checkboxes, to act on them together, and current for the one row a button in it made current. A row can be both, and looks the same as either. Rows cannot be reordered: the table draws no drag handle, and draggable draws nothing. In the filled look (look="filled"), the arrow of a column not sorted is unseen until a reader goes to it: it shows while a pointer is over its heading and while the keyboard is on its sort button, and it keeps its room, so the heading does not move as it comes and goes. On a touch screen it shows once the column is sorted. The sort button itself never hides. The flat look (look="flat") keeps every behavior here but the two forms for big tables: their sticking cells paint a surface of their own, which a card around the table would not match. Given sticky-header or pin-first with it, the table draws without them and warns in development. A table wider than its place scrolls sideways inside its frame, so no column is ever hidden; with the first column pinned, every row keeps its subject in view while it does. A long table given a lower frame scrolls inside it with its headings in view. Nothing animates.

A table wider than its frame scrolls sideways inside it, and a long table given a lower frame scrolls down inside it, with the system's scrollbar: a thin, see-through bar over its rows that takes no room and shows while the mouse is over it or it scrolls. While the bar is hidden it is not laid out at all, so a page that measures how far the frame runs past its width just after narrowing it gets the table's own width; while the bar shows, it follows the frame's new size a moment later. Where no scrollbar takes room (the system's, or the browser's on a phone or a trackpad), a thin bar along the frame's foot also shows that there is more to the side and how much of the table is in view, and follows the scroll; it holds the frame's visible edges, and in a long table its foot, just over the table's foot. It fades while the system's scrollbar shows, in the same strip, so there is never a second bar.

A column takes its width from what its rows hold, so when the rows change, the columns move. Give a column a width and the table keeps every column's width instead: filtering, paging, showing more rows, opening a row or swapping the rows for others moves no column, because the table no longer reads its rows to size them. A width is a share of the table (share- and the percent), a size token (its path, which starts size-) or rest, the room the other columns leave, which columns taking it share equally. A column given no width in such a table takes the rest too, and there is no way to give a number. Give one column rest and the others a width, and that column takes whatever the table has over.

What a cell holds then wraps inside its column and its row grows; a word too long for the column breaks; a heading too long for it takes a second line. A tag, a button and a picture keep their size, so a column holding one needs room for it. A prose column takes the width it is given, and its lines still stop at its widest measure.

A table with column widths narrows with its frame down to a least width, and past that its frame scrolls it sideways, as any table too wide for its frame does. So a column taking the rest keeps its room on a phone, and shares stay readable. minWidth on the table gives it another least width, as a size token, or none, for a small table that may narrow with its frame. The least width has to leave room for the column taking the rest beside the columns with a size token: when they fill it, that column has nothing left. A table that gives no column a width sizes itself from its rows, as it always has.

A column can be headed by a block rather than a line: give it tallHeader, and header holds a few lines and buttons, such as a value over its description and a row of buttons. Its heading grows to fit, the heading row is as high as the tallest heading in it, and the rule runs under that one. A column without tallHeader keeps its one line. The block's lines stack and wrap, and its column stays wide enough to read them while a table too wide for its frame narrows, so the frame scrolls sideways instead. A column with a width sets the block's width, so give it room for the block's buttons.

A block can hold buttons of its own, and a button cannot hold a button, so in a tall heading the block sits outside the sort button. The arrow alone sorts, after the block, or close before it in a column lined up at the end, with the filter and info buttons after it on the block's first line. The block's own buttons do not sort. In the filled look a tall heading's arrow stays in view, since it has no words to press.

In the long forms, what is scrolled into view lands clear of the heading, however high it stands, and the same goes for a long heading wrapping in a column with a width. A heading that would take more than half of what shows, the frame or in the page what scrolls it, would cover the rows it heads, so it stops holding: it stays at the table's top and scrolls away with the rows until there is room for it again. A pinned first column stays pinned, and the foot stays at the foot.

A row can be pressed as a whole, to open what it stands for, and is a plain row until a page says what the press does. Give it href and it holds a real link, so opening in a new tab, the link's menu and every other way of following a link work from anywhere on the row. Give it onClick and it holds a real button. Give it both, for a page that moves between its own pages, and the link calls onClick, which can stop the browser going there. A disabled row takes no press. A press on a control in the row, a checkbox, a caret, a link, a button or anything else that takes a press, does that control's job and never the row's, so the page's own handlers have nothing to stop. A disabled control keeps the press too and does nothing with it. A swipe over a row scrolls, as it does anywhere.

The row takes the hover fill as every row does, and the chosen row's fill while it is pressed. While what it opened is open, the page marks it current, which keeps that fill, with the pointer still resting on the row too, and tells screen readers, on the row and on its link or button. What opens over the page, such as a dialog, is named by the row's aria-haspopup.

A press on a row can open a popover with no opener of the page's own, since the row's own link or button can be that opener. Give the table an id, and give the popover openerId, the id of the row's link or button, which tableRowPressId(id, row.id) gives. Put the popover in one of the row's cells. The row's onClick turns the popover's open over, and the page marks the row current while it is open and gives the row aria-haspopup="dialog". The row stays one Tab stop. Opening moves focus into the panel, Escape and the close button put it back on the row's own link or button, and another press on the row closes the panel. The panel hangs on the whole row, so it opens under the row's start. To hang it on the row's name, wrap the name in a div with an id and pass that as the popover's hangOnId: a span around a cell's value has only the first line's box, so the panel would cover the description. With the first column held, a press on the held cell does all this too, and focus never stays on the held cell's copy, which Tab and screen readers leave out.

It works beside everything else a row does. In a table with checkboxes, a press on the row opens it and only its checkbox chooses it, so a row can be open and chosen apart. A row with a detail keeps its caret, or the page's button in a cell, for the detail, and a press elsewhere on the row does the row's own job; the detail row is no part of the press. It works in every look and form. With the first column held (pin-first), the held cell takes the press too, and a control passing under it stays out of reach, as in every held table.

The filled look takes every form and behavior here. Its fills are see-through, so a table takes them on any surface; a heading or a foot that sticks or moves lays them over the page canvas, so the rows pass under it unseen, which suits a table on the canvas. Its foot grows with what it holds and wraps on a phone, so nothing in it runs past the frame, and it stays in place while a wide table scrolls sideways, its fill under the whole of what shows.

A table takes the density of the page it sits on. density="compact" makes one table compact in a comfortable page: it writes data-density="compact" on the frame, and everything inside the frame then takes the compact steps of the design tokens, exactly as it would on a compact page. The rows, the heading row and the foot step down together, as does what they hold: a pagination's buttons and select, a small button, the room at a tag's sides. A tag keeps its height, as it does at either density. Nothing outside the frame changes, and on a page that is compact already the option changes nothing. A row with a choose box and a caret at its start, or an avatar in a cell, stands a little taller than a plain one, as it does on a compact page.

What the table opens over the page keeps the table's density, wherever it shows, because it stays inside the frame in the page's structure: a heading's filter list, its info popover, and the list of the select in the foot. A control of the page's own in a cell or a detail row is compact too, and so is a dialog the page draws there; draw it outside the table to give it the page's density. The density is the page's to set, never a control for the reader.

Every look and form takes it. The sort, filter, info, caret and copy buttons keep their size and their focus rings. On a touch screen their press areas still stop at their own row, so they are as tall as the lower row, and never less than the least a press needs.

In the long form in a frame of its own, the table's foot stays at the frame's foot while the rows scroll under it, as the heading row stays at its top, and what the frame scrolls into view, such as a focused button, lands clear of both.

In the long form in the page, the frame never scrolls downwards, so every turn of the wheel and every swipe over the table moves the page. As the page scrolls, the heading row's real cells move down to stay just under the room the page keeps clear at the top of what scrolls it, and stop where the table ends; the foot, the scroll hint and the scrollbar's sideways bar move up to the foot of what shows, never above the heading row's own place. That room is the scroll padding at the top (scroll-padding-top) of what scrolls the table: the window, or the nearest part around the table that scrolls or clips what runs past it. The system's app bar that hides on scroll keeps its room there while it shows, and the heading rises into the room while the bar has slid away. The table still scrolls sideways inside its frame, its heading cells with their columns, and a pinned first column stays pinned. Where the browser can tie the move to the scroll itself, it moves them as it scrolls; elsewhere the table's script moves them on each scroll. What the page scrolls into view in the rows, such as a focused button, lands clear of the heading and the foot. Without the table's script, nothing moves and the table reads as a plain long one.

A reader can press the heading anywhere down a long table, and a browser that holds a scroll to a row on the screen would follow the row a sort moved. So in both long forms nothing in the frame anchors a scroll (overflow-anchor: none): a sort leaves the page, or the frame, where it is, and a site needs no rule of its own for it. The same goes for a row that opens or grows above what shows: the rows under it move down, and the scroll does not make up for it.

On a touch screen the sort, copy and caret buttons, the heading's filter and info buttons and a choose box keep their size and take an invisible press area round them, out to the comfortable touch target but never past their own row; side by side in a cell, they reach only halfway to each other. In a table with a pinned first column, what scrolls into view stops beside the pinned column, never under it.

Accessibility


What it does for you

  • It is a real table with a caption, column headings with scope, and sort state on the sorted heading.
  • The choose-all checkbox is named, shows a mixed state when some rows are chosen, and each row's checkbox is named after the row. A row that cannot be chosen has no checkbox, so nothing that does nothing is reached, and the choose-all checkbox counts only the rows that can be.
  • The info button on a heading is a real button that Tab reaches, named for what it explains ("About Budget"); Enter, Space, a click or a tap opens its popover, and Escape returns focus to it.
  • The filter button is named for its column and says how many filters apply ("Filter Stage, 2 applied"), so the count is heard, not only seen. Its list is a menu of items that tick, moved through with the arrow keys and ticked with Space, and Escape closes it and returns focus.
  • In the filled look a sort button is always in the tab order with its name, whether or not its arrow shows, and the sorted heading says its direction. The arrow is decoration for the eye: the state is on the heading.
  • A row's caret is a button named for its row ("Details for Brand refresh") that says whether the row is open and points at the detail row; a closed detail row is hidden, so it is out of the reading order. The detail row is named the same way, so it is heard as that row's detail.
  • A group heading is a heading cell for the rows under it, each group a body of its own.
  • A pinned row is told by its pin, which says "Pinned" to screen readers, not by its edge alone.
  • A cell's bar is hidden from screen readers; the figure beside it is the value.
  • A chosen row is told by its ticked checkbox, not by its fill alone.
  • A tall heading's sort button is a real button named for its column ("Sort by Plan A"), and the sorted heading says its direction. The heading gives a screen reader the column's label in place of the whole block (abbr), for those that say a column's heading before each of its cells.
  • The current row tells screen readers it is the current one (aria-current="true" on its row element), beside its fill.
  • A row that can be pressed stays a plain table row, read cell by cell as any other, with no new keys to learn. What presses it is a real link or button, one Tab stop named for what it does and for its row ("Open invoice INV-2042"), with a focus ring round the whole row. It comes first in the row, or after the first cell's controls where that column is held.
  • While a row that can be pressed is current, its link or button says so too (aria-current="true"), so it is heard where the focus is.
  • A row that opens a popover stays one Tab stop. Focus moves into the panel as it opens, and Escape and the close button put it back on the row's own link or button, a press on a held cell included.
  • In the long form in a frame of its own, the frame scrolls up and down, so it is a region named after the caption that Tab can reach, and the arrow keys scroll it.
  • In the long form in the page, the heading row is the real one wherever it shows, so its sort buttons are read and pressed there, and a control focused in the rows lands clear of the heading and the foot. The frame never scrolls downwards, so it is a region and a Tab stop only while it scrolls sideways.
  • In both long forms, a control focused in the rows lands clear of a heading taller than one line. A heading that would take more than half of what shows scrolls away with the rows, so it never hides the control the keyboard is on.
  • In a compact table the sort, filter, info, caret and copy buttons are as tall as in any other table, and take the same focus ring.
  • A table too wide for its frame scrolls sideways inside it; while it does, the frame is the same named region that Tab reaches. A frame with nothing to scroll takes no Tab.
  • The scroll hint is decoration: hidden from assistive technology, and it takes no press; the table scrolls as it always does.
  • On a touch screen the sort, copy and caret buttons, the heading's filter and info buttons and a choose box take an invisible press area out to the comfortable touch target, never past their own row.

What you must do

  • Give the table a caption.
  • When some rows cannot be chosen, say why near the table or in the row, such as its state: the missing checkbox is not explained by itself.
  • In markup written by hand, put an empty smbk-table__choose-space element where a row that cannot be chosen would have its checkbox.
  • Say in words, from the page, why a row is highlighted: a highlighted row's tint says nothing to a screen reader.
  • Say in words, in the current row, that it is the current one, such as "Fitted" or "Your plan". Not every screen reader announces the current mark on a table row, and the fill alone says nothing.
  • In markup written by hand, give the current row the row's current modifier and aria-current="true".
  • When a button of your own in a cell opens a row's detail (detailTrigger="cell"), give it aria-expanded, aria-controls set to the detail row's id (tableDetailId(id, row.id), with the table's id), and a name that says what it opens and for which row, its visible words included, such as "Brake pads fits 6 models".
  • In markup written by hand, a table whose rows open from such a button has no lead part, and a closed detail row takes hidden.
  • In markup written by hand, a table with column widths gives its table part the columns-fixed modifier, and holds a colgroup straight after its caption with one col per column, each the column part with its width modifier, such as smbk-table__column--width-rest. A least width is the table part's min-width modifier.
  • In markup written by hand, a filled table's frame takes the look's modifier, smbk-table--look-filled, and its sorted heading the smbk-table__header--active modifier beside aria-sort: the modifier is what keeps that column's arrow showing. Nothing else in the markup changes.
  • Give a column with tallHeader a label, the heading in words: it names the arrow, the filter and the info button, and without one they are named from the column's key. A page in another language gives the words of the sort button's name in sortLabel.
  • Keep heading elements (h1 to h6) out of a block: a table's heading cell cannot hold one. Give the line the text style it needs.
  • In markup written by hand, a tall heading's header part takes the tall modifier, smbk-table__header--tall. Its header-cell part is a div holding the block, a div with the header-block part. A sort button in it holds the arrow alone and needs an aria-label, and the heading its abbr.
  • In markup written by hand, a long table in a frame of its own has no script to measure a tall heading: set the frame's scroll-padding-top to the heading's height, so what scrolls into view lands clear of it.
  • Give a row that can be pressed a label, which names its link or button, and pass rowPressLabel when the press does something other than open the row, such as "Edit invoice INV-2042". Without a label a screen reader hears the row's id.
  • Show that rows can be pressed in more than the hover fill, which a touch screen does not have: say so near the table, or end each row with a caret.
  • When a press on a row opens a popover, give the row aria-haspopup="dialog" and mark it current while the panel is open, which is how its link or button says so.
  • In markup written by hand, give a row that can be pressed the row's pressable modifier, and as the first thing in its first cell an empty link or button with the smbk-table__row-press class and an aria-label. It needs no script. In a table with its first column held, that link or button goes first in the second cell, and the first cell takes a copy of it with tabindex="-1" and aria-hidden="true", so the held cell takes the press too.
  • In markup written by hand, a compact table's frame takes data-density="compact". Nothing else in the markup changes.
  • Give the page's own words for everything the table says by itself, such as the names of its checkboxes, carets, pins, filter, info and copy buttons, and of what a row's press does, which keep their English until you do; a page in another language can change them.

How to test

  • Tab through a heading: the sort, filter and info buttons each take focus with a focus ring, and Enter or Space presses them. In the filled look a sort button's arrow shows as it takes focus, and the heading does not move. Open a filter list, move with the arrow keys, tick with Space, and press Escape: the list closes and focus returns to the filter button.
  • Tab to a row's caret, or to the button in a cell that opens its detail, and press Enter or Space: the detail row opens and the button says it is open.
  • Press a row that can be pressed on its words, on its empty room and on a picture or a bar: it opens. Press its checkbox, its caret and a button in it: each does its own job, and the row does not open.
  • Tab to a row that can be pressed: a ring runs round the whole row, no column moves, and Enter opens it. On a row with a link, open it in a new tab from anywhere on the row.
  • On a row that opens a popover, press Enter: the panel opens and focus moves in. Press Escape: it closes, and the ring is back round the row. Press the row twice with the pointer: the second press closes the panel and it stays closed.
  • With a screen reader, a row that can be pressed is read as a plain row, and its link or button by what it does and the row's name, and as current while what it opened is open.
  • With a screen reader, the table is read with its caption and headings, the sorted heading with its direction, each row's checkbox by the row's name, and the filter button with its count.
  • In a forced-color mode, where the fills are gone, the current row's words still say it is the current one.
  • In a table wider than its frame or taller than its place, Tab reaches the frame by its name, and the arrow keys scroll it.
  • In the long form in the page, scroll with the wheel over the table: the page moves every time, and the heading row stays in view. Tab through the rows' buttons: each shows clear of the heading and the foot. Press a sortable heading far down the table: the page stays where it is, as a frame of its own does in the other long form.
  • In a table with a tall heading, Tab through the heading: the block's own buttons, then its arrow, each with a focus ring. In a long form, Tab on through the rows' buttons: each shows clear of the heading. Lower the frame or the window until the heading takes more than half of it: the heading scrolls away with the rows.
  • On a touch screen, the small buttons take a press just outside their own edge.
  • In a compact table on a comfortable page, Tab through its buttons: each takes the same focus ring as on any other table. Open a heading's filter list: it is as tight as the table, and a list opened from outside the table is not.

Keyboard

Table keys
KeyWhat it does
Tab
Moves focus through the heading's sort, filter, info and copy buttons, the row checkboxes and carets, a block's or a cell's own buttons, the link or button of a row that can be pressed, and the scrolling frame when it has something to scroll.
EnterorSpace
On a button (sort, filter, info, caret, copy, a block's or a cell's own, a row's), presses it.
Enter
On the link of a row that can be pressed, follows it.
Space
On a checkbox, ticks or clears it.
Down ArroworUp Arrow
In a filter list, moves to the next or previous item.
Space
On an item of a filter list, ticks it and leaves the list open.
Escape
Closes a filter list or the info popover and returns focus to its button. Closes a popover a row opened and returns focus to the row's own link or button.
Arrow keys
On the scrolling frame, scroll it.

Source


The files the table is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
table.css
/* smbk-table — Table. Figma page "Table" 4312:13783: "Table" 4318:16988 over "TableHeader" 4318:11164,
 * "TableHeaderCell" 4313:6100, "TableRow" 4318:11168, "TableRowCell" 4313:9290 and "TableFooter" 4318:11163.
 * Three looks. Framed, the default: a bordered, rounded table, a header row on the default surface with a
 * control-colored rule under it, hairlines between header cells, rows with
 * a subtle rule under each that take the hover fill under the pointer and their own fill when chosen or current,
 * and a foot for the pagination under a strong rule. Flat, for a table inside a card: no frame, no heading surface,
 * a subtle rule over every row, no room at the table's sides, and rows one line of text high. Filled, the framed table
 * with a calmer heading: the heading row and the foot on a neutral fill, group rows sunken and low, no hairlines
 * between headings, and an arrow only where a reader is sorting. Numbers take tabular digits. A heading may line up at the end
 * and carry a filter and an info button; a row may be pinned, highlighted, ruled off, open a detail row under it, or
 * be a group heading; a column may hold prose that wraps, or keep a width of its own; a cell may hold a figure with a bar;
 * and a table whose cells differ in height may line them up at the top of their rows. */

/* Positioned so the visually hidden caption is placed inside the frame; left to the page, it would sit
   at the far end of a long scrolling area and stretch the document. The frame scrolls a table too wide for it. */
.smbk-table {
  box-sizing: border-box;
  position: relative;
  display: block;
  width: 100%;
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-sm);
  color: var(--smbk-color-text-default);
  /* A table wider than its place scrolls sideways inside the frame; hiding the overflow would hide data. */
  overflow-x: auto;
  /* The system's scrollbar: thin, in the subtle border color, on a clear track. */
  scrollbar-width: thin;
  scrollbar-color: var(--smbk-color-border-subtle) transparent;
}

.smbk-table__table {
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
}

/* .smbk-table__caption: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

/* ---- header ---- */

.smbk-table__header-row {
  background: var(--smbk-color-surface-default);
}

.smbk-table__header {
  box-sizing: border-box;
  height: var(--smbk-size-control-md);
  padding: var(--smbk-space-inset-md) var(--smbk-space-inset-lg);
  border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-control);
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-label-sm-font-family);
  font-size: var(--smbk-typography-label-sm-font-size);
  font-weight: var(--smbk-typography-label-sm-font-weight);
  line-height: var(--smbk-typography-label-sm-line-height);
  letter-spacing: var(--smbk-typography-label-sm-letter-spacing);
  text-transform: var(--smbk-typography-label-sm-text-transform);
  text-align: start;
  vertical-align: middle;
  white-space: nowrap;
}

.smbk-table__header + .smbk-table__header {
  border-left: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
}

.smbk-table__header-cell {
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  height: var(--smbk-size-mark-md);
}

.smbk-table__header-text {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-xs);
}

.smbk-table__header-icon {
  flex-shrink: 0;
  font-size: var(--smbk-size-icon-xs);
}

/* The sort button reaches 24 px tall, the least a press needs, past the heading's line: its margins give the
   room back, so the heading row keeps its height. */
.smbk-table__sort {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  min-height: var(--smbk-size-target-min);
  margin: calc((var(--smbk-size-mark-md) - var(--smbk-size-target-min)) / 2) 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
}

.smbk-table__header--active {
  color: var(--smbk-color-text-default);
}

/* The sort arrow: up or down on the sorted column, both ways on the others, in the subtle text color. */
.smbk-table__sort > .smbk-table__header-icon {
  color: var(--smbk-color-text-subtle);
}

/* A column lined up at the end, for figures: the heading, its arrow (before the label) and its buttons move to the end. */
.smbk-table__header--align-end {
  text-align: end;
}

.smbk-table__header--align-end .smbk-table__header-cell {
  justify-content: flex-end;
}

/* The filter and info buttons: the extra-small ghost icon button, 24 tall in the 20 px heading line; their margins
   give the room back, so the heading row keeps its height. */
.smbk-table__header-cell .smbk-table__filter,
.smbk-table__header-cell .smbk-table__info {
  margin-block: calc((var(--smbk-size-mark-md) - var(--smbk-size-control-xs)) / 2);
}

/* How many filters apply, close after the funnel. The button's name says the same in words. */
.smbk-table__filter-count {
  margin-inline-start: calc(var(--smbk-space-inline-xs) - var(--smbk-space-inline-md));
}

@media (hover: hover) {
  .smbk-table__sort:hover {
    color: var(--smbk-color-text-default);
  }
}

.smbk-table__sort:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
  border-radius: var(--smbk-radius-control-xs);
}

/* ---- rows ---- */

.smbk-table__row {
  border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
}

.smbk-table__row:last-child {
  border-bottom: 0;
}

/* Group headings cut the rows into bodies of their own: the last row of each body but the last keeps its rule, and a
   row whose closed detail row ends the body takes none, as the last row. */
.smbk-table__table > tbody:not(:last-child) > .smbk-table__row:last-child {
  border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
}

.smbk-table__table > tbody:last-child > .smbk-table__row:has(+ .smbk-table__detail-row[hidden]:last-child) {
  border-bottom: 0;
}

/* An open row and its detail read as one: no rule between them. The detail row keeps the rule under it. */
.smbk-table__row:has(+ .smbk-table__detail-row:not([hidden])) {
  border-bottom: 0;
}

@media (hover: hover) {
  .smbk-table__row:hover {
    background: var(--smbk-color-surface-hover);
  }

  /* A group heading and an open row's detail are not records: no hover fill. */
  .smbk-table__row--type-group:hover,
  .smbk-table__detail-row:hover {
    background: none;
  }
}

/* A chosen row: its own fill, beside the ticked checkbox that says so without color. The current row, the one the
   page's own button in it acted on, in a table with or without checkboxes, takes the same fill; its row says so to a
   screen reader (aria-current) and the page in words. Each keeps its fill under the pointer: a press with a mouse
   leaves the pointer on the row it made current, and a hover fill there would say it had not. A description on either
   takes the subtle text color, which keeps 4.5:1 on the fill where the subtlest does not. */
.smbk-table__row--selected,
.smbk-table__row--current,
.smbk-table__row--selected:hover,
.smbk-table__row--current:hover {
  background: var(--smbk-color-action-default-background-pressed);
}

.smbk-table__row--selected .smbk-table__description,
.smbk-table__row--current .smbk-table__description {
  color: var(--smbk-color-text-subtle);
}

/* A highlighted row, a linked or matching record: the info tint. Its descriptions take the subtle text color too,
   which keeps 4.5:1 on the tint. The tint says nothing to a screen reader: the page says why in words. */
.smbk-table__row--highlighted {
  background: var(--smbk-color-feedback-info-surface-subtle);
}

.smbk-table__row--highlighted .smbk-table__description {
  color: var(--smbk-color-text-subtle);
}

/* A pinned row: a brand edge on its start side, beside the pin in its leading room that says so without color. */
.smbk-table__row--pinned > .smbk-table__cell:first-child {
  box-shadow: inset var(--smbk-border-width-accent) 0 0 0 var(--smbk-color-border-brand);
}

.smbk-table__row--pinned > .smbk-table__cell:first-child:dir(rtl) {
  box-shadow: inset calc(-1 * var(--smbk-border-width-accent)) 0 0 0 var(--smbk-color-border-brand);
}

/* A strong rule above a row separates groups. It is drawn by the row's cells, so it wins over the row above's rule. */
.smbk-table__row--rule-above > * {
  border-top: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}

/* A group heading: one cell across the row, its label and a count in the subtle color, no fill of its own. */
.smbk-table__group {
  box-sizing: border-box;
  height: var(--smbk-size-control-md);
  padding: var(--smbk-space-inset-sm) var(--smbk-space-inset-lg);
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-transform: var(--smbk-typography-label-md-text-transform);
  font-variant-numeric: tabular-nums;
  text-align: start;
  vertical-align: middle;
  white-space: nowrap;
}

.smbk-table__group-label {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-sm);
}

.smbk-table__group-count {
  color: var(--smbk-color-text-subtle);
}

/* The row a row unfolds: one cell across every column on the sunken surface, its text starting where the group
   heading's does. Closed, it is hidden, so it is out of the reading order. A caret at the row's start opens it, or a
   button of the page's own in one of its cells, which then draws no caret and no leading room. */
.smbk-table__detail {
  box-sizing: border-box;
  padding: var(--smbk-space-inset-lg);
  background: var(--smbk-color-surface-sunken);
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  text-align: start;
  vertical-align: top;
  white-space: normal;
}

.smbk-table__detail > p {
  margin: 0;
}

/* Its title sits straight over the text under it: the line heights give the air. */
.smbk-table__detail-title {
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
  letter-spacing: var(--smbk-typography-label-md-letter-spacing);
  text-transform: var(--smbk-typography-label-md-text-transform);
}

.smbk-table__row--disabled {
  color: var(--smbk-color-text-disabled);
}

/* Pictures, tags and links inside a disabled row fade with the disabled-media opacity, as Figma dims them. */
.smbk-table__row--disabled .smbk-table__cell > * {
  opacity: var(--smbk-opacity-disabled);
}

/* A row is at least one medium control high (40, or 32 in compact); what a cell holds is one small control high and
   sits centered in it, as Figma's TableRowCell does. The room above and below is padding, half of what the row has
   over the small control, so a row of one line keeps its height and taller content grows the row with that room
   kept (a cell's height is only its least). */
.smbk-table__cell {
  box-sizing: border-box;
  height: var(--smbk-size-control-md);
  padding: calc((var(--smbk-size-control-md) - var(--smbk-size-control-sm)) / 2) var(--smbk-space-inset-lg);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  font-variant-numeric: tabular-nums;
  vertical-align: middle;
  white-space: nowrap;
}

/* A column lined up at the end, for figures: they line up on their last digit. */
.smbk-table__cell--align-end {
  text-align: end;
}

/* A column of prose: sentences wrap between the prose measures, and the first line sits where a one-line value's
   does. The other cells of its row align to the top with it. */
.smbk-table__cell--type-prose {
  height: auto;
  padding-block: calc((var(--smbk-size-control-md) - 1lh) / 2);
  vertical-align: top;
  white-space: normal;
}

.smbk-table__prose {
  min-width: var(--smbk-size-table-prose-min);
  max-width: var(--smbk-size-table-prose-max);
}

.smbk-table__row:has(> .smbk-table__cell--type-prose) > .smbk-table__cell:not(.smbk-table__cell--type-prose) {
  padding-block-start: calc((var(--smbk-size-control-md) - var(--smbk-size-control-sm)) / 2);
  vertical-align: top;
}

/* Rows aligned to the top, for cells of very different heights: every cell starts at the top of its row, and its first
   line sits where a one-line value's does, so the eye reads across the row. A row of one line looks as it always does.
   The two lengths say where that first line is: its room, one small control high, and how far down the row that room
   starts. A control keeps the room to itself, and a line of words sits in the middle of it. */
.smbk-table--align-top {
  --smbk-table-line: var(--smbk-size-control-sm);
  --smbk-table-line-start: calc((var(--smbk-size-control-md) - var(--smbk-size-control-sm)) / 2);
}

.smbk-table--align-top .smbk-table__cell:not(.smbk-table__cell--type-prose) {
  padding-block-start: var(--smbk-table-line-start);
  vertical-align: top;
}

/* Words or a block of the page's own in a cell, with none of the table's pieces to center them. */
.smbk-table--align-top .smbk-table__cell:not(.smbk-table__cell--type-prose, :has(.smbk-table__content, .smbk-table__start)) {
  padding-block-start: calc(var(--smbk-table-line-start) + (var(--smbk-table-line) - 1lh) / 2);
}

/* Set at the line's top, a piece cannot be pushed down by its own baseline. */
.smbk-table--align-top :is(.smbk-table__content, .smbk-table__start) {
  vertical-align: top;
}

/* A value or a link that wraps keeps its first line in that room, its other lines under it. */
.smbk-table--align-top :is(.smbk-table__value, .smbk-table__content > .smbk-link) {
  margin-block: calc((var(--smbk-table-line) - 1lh) / 2);
}

.smbk-table--align-top .smbk-table__value {
  align-items: flex-start;
}

/* What stands beside those lines stays with the first of them: the icon before a value, the copy button after it, and
   a row's caret, pin and choose box. */
.smbk-table--align-top .smbk-table__value-icon {
  margin-block-start: calc((1lh - var(--smbk-size-icon-sm)) / 2);
}

.smbk-table--align-top .smbk-table__start {
  align-items: flex-start;
}

.smbk-table--align-top .smbk-table__lead {
  min-height: var(--smbk-table-line);
}

.smbk-table--align-top .smbk-table__content > :is(.smbk-table__copy, .smbk-checkbox) {
  align-self: flex-start;
  margin-block-start: calc((var(--smbk-table-line) - var(--smbk-size-target-min)) / 2);
}

/* The first cell's leading room, in a table with a caret that opens a row or a pinned row: one small icon wide in every
   row and in the heading, so the columns line up, holding the caret or the pin. A row opened by the page's own button
   in a cell has no caret, and needs no room. */
.smbk-table__start {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-lg);
}

.smbk-table__lead {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: var(--smbk-space-inline-xs);
  min-width: var(--smbk-size-icon-sm);
}

.smbk-table__header-cell > .smbk-table__lead {
  margin-inline-end: calc(var(--smbk-space-inline-lg) - var(--smbk-space-inline-md));
}

.smbk-table__pin {
  color: var(--smbk-color-text-subtle);
  font-size: var(--smbk-size-icon-sm);
}

/* The caret that opens a row: the glyph inside a 24 px box, the least a press needs; the box's margins give the
   room back. It points to the end while closed and down while open. */
.smbk-table__expander {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--smbk-size-target-min);
  height: var(--smbk-size-target-min);
  margin: calc((var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2);
  padding: 0;
  border: 0;
  background: none;
  color: var(--smbk-color-text-subtle);
  font-size: var(--smbk-size-icon-sm);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.smbk-table__expander[aria-expanded='false']:dir(rtl) > * {
  transform: scaleX(-1);
}

@media (hover: hover) {
  .smbk-table__expander:hover {
    color: var(--smbk-color-text-default);
  }
}

.smbk-table__expander:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
  border-radius: var(--smbk-radius-control-xs);
}

/* The content of a cell: a row of its pieces, one small control high. */
.smbk-table__content {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  min-height: var(--smbk-size-control-sm);
}

/* A row that cannot be chosen, in a table whose rows can: the room the small choose box takes, its box and the room
   the choice row keeps after it, kept empty, so the row's first value lines up with the others'. */
.smbk-table__choose-space {
  flex: none;
  width: calc(var(--smbk-size-selection-sm) + var(--smbk-space-inline-sm));
}

/* An icon before the value sits the small inline gap from it, as a 16 icon beside 14 px text does across the system. */
.smbk-table__value {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-sm);
}

.smbk-table__value-icon {
  flex-shrink: 0;
  font-size: var(--smbk-size-icon-sm);
}

/* A cell holding a value over its description keeps a medium inset above and below, so the two lines never run up
   to the rules: the row grows to hold them. */
.smbk-table__cell:has(.smbk-table__stack) {
  padding-block: var(--smbk-space-inset-md);
}

/* A value over its description: no gap, the line heights give the air. */
.smbk-table__stack {
  display: inline-flex;
  flex-direction: column;
  gap: var(--smbk-space-text-label);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

.smbk-table__description {
  color: var(--smbk-color-text-subtlest);
}

/* In a row aligned to the top, the value over its description is smaller type than a one-line value. Half the difference
   of their line heights and of their sizes further down, its letters stand on the same line as those beside it. Under
   it, the cell's own inset. */
.smbk-table--align-top .smbk-table__value:has(> .smbk-table__stack) {
  margin-block-end: 0;
}

.smbk-table--align-top .smbk-table__stack {
  margin-block-start: calc((var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height) - 1lh + var(--smbk-typography-body-sm-font-size) - 1em) / 2);
}

/* The copy glyph, inside a 24 px box, the least a press needs; the box reaches past the glyph and its
   margins give the room back, so the value does not move. */
.smbk-table__copy {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--smbk-size-target-min);
  height: var(--smbk-size-target-min);
  margin: calc((var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2);
  padding: 0;
  border: 0;
  background: none;
  color: var(--smbk-color-text-subtle);
  font-size: var(--smbk-size-icon-sm);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

@media (hover: hover) {
  .smbk-table__copy:hover {
    color: var(--smbk-color-text-brand);
  }
}

.smbk-table__copy:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
  border-radius: var(--smbk-radius-control-xs);
}

/* A figure with a bar after it: the progress track and fill, in the default tone, at a fixed length. The figure is
   the value; the bar is decoration for the eye. */
.smbk-table__figure {
  flex-shrink: 0;
}

.smbk-table__content > .smbk-table__bar {
  flex-shrink: 0;
  width: var(--smbk-size-80);
}

/* ---- foot ---- */

.smbk-table__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--smbk-space-inline-md);
  box-sizing: border-box;
  min-height: var(--smbk-size-control-lg);
  padding: var(--smbk-space-inset-xs) var(--smbk-space-inset-lg);
  border-top: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}

/* ---- the flat look: a table inside a card ----
   No frame and no corner, no fill on the heading row and no hairlines between its headings, a subtle rule over every
   row, the heading row's included, and no room at the table's first and last cell, so its words line up with the
   card's other lines. Each row is one line of the body text with a small inset above and below, the heading row as
   high as the others; the headings are in the small body style. The frame is still there, unseen, and still scrolls a
   table too wide for it, with its scrollbar and its scroll hint. A flat table takes neither the long nor the pinned
   form (the wrapper drops them): their sticking cells paint a surface of their own, which a card would not match. */

.smbk-table--look-flat {
  border: 0;
  border-top: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: 0;
}

.smbk-table--look-flat .smbk-table__header-row {
  background: none;
}

.smbk-table--look-flat .smbk-table__header,
.smbk-table--look-flat .smbk-table__cell {
  height: auto;
  padding: var(--smbk-space-inset-sm) 0;
}

/* Between columns, the room a cell keeps before its words; none before the first or after the last. */
.smbk-table--look-flat .smbk-table__header + .smbk-table__header,
.smbk-table--look-flat .smbk-table__cell + .smbk-table__cell {
  padding-inline-start: var(--smbk-space-inline-md);
}

.smbk-table--look-flat .smbk-table__header {
  border: 0;
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
  text-transform: var(--smbk-typography-body-xs-text-transform);
}

/* The heading's line is as high as a row's, so the heading row is as high as the rows under it. */
.smbk-table--look-flat .smbk-table__header-cell {
  height: calc(var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height));
}

.smbk-table--look-flat .smbk-table__row {
  border-top: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-bottom: 0;
}

/* An open row's detail joins it, with no rule over it; the next row's rule runs under it. */
.smbk-table--look-flat .smbk-table__detail-row {
  border-top: 0;
}

/* What a cell holds sets its row's height: a line of text, or a control with the inset above and below it. Set at the
   line's top, an icon before a value cannot push the line down by its own baseline. */
.smbk-table--look-flat .smbk-table__content,
.smbk-table--look-flat .smbk-table__start {
  min-height: 0;
  vertical-align: top;
}

/* A prose column's first line, and the other cells of its row, start the same inset down. */
.smbk-table--look-flat .smbk-table__row:has(> .smbk-table__cell--type-prose) > .smbk-table__cell {
  padding-block-start: var(--smbk-space-inset-sm);
}

/* Aligned to the top, a flat row's first line is that one line of text, the same inset down. */
.smbk-table--look-flat.smbk-table--align-top {
  --smbk-table-line: calc(var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height));
  --smbk-table-line-start: var(--smbk-space-inset-sm);
}

.smbk-table--look-flat .smbk-table__group {
  height: auto;
  padding-inline: 0;
}

.smbk-table--look-flat .smbk-table__footer {
  padding-inline: 0;
}

/* ---- columns with a width of their own ----
   A table whose columns name their widths, in the column elements at its top, keeps them whatever its rows hold: the
   browser lays such a table out from those elements alone and never reads a row, so filtering, paging or opening a row
   moves no column. A column takes a share of the table, a size token, or the room the others leave. What a cell holds
   wraps inside its column, as the widths no longer follow it; a word too long for the column breaks, and the row grows.
   The table stops narrowing at its least width, past which its frame scrolls it sideways, so a column taking the room
   that is left keeps some on a phone. A table that names no width has none of this, and sizes itself as it always has. */

.smbk-table__table--columns-fixed {
  table-layout: fixed;
  min-width: var(--smbk-size-width-480);
  overflow-wrap: anywhere;
}

.smbk-table__table--columns-fixed :is(.smbk-table__header, .smbk-table__cell, .smbk-table__group) {
  white-space: normal;
}

/* A heading too long for its column takes a second line, its row growing with it. */
.smbk-table__table--columns-fixed .smbk-table__header-cell {
  height: auto;
  min-height: var(--smbk-size-mark-md);
}

.smbk-table--look-flat .smbk-table__table--columns-fixed .smbk-table__header-cell {
  min-height: calc(var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height));
}

.smbk-table__table--columns-fixed .smbk-table__sort {
  text-align: inherit;
}

/* The column sets a prose cell's width, so its narrowest measure no longer holds; its widest still keeps lines readable. */
.smbk-table__table--columns-fixed .smbk-table__prose {
  min-width: 0;
}

/* What a cell lines up stops at its column, and a figure's bar gives way before its figure. */
.smbk-table__table--columns-fixed :is(.smbk-table__content, .smbk-table__start) {
  max-width: 100%;
}

.smbk-table__table--columns-fixed .smbk-table__content > .smbk-table__bar {
  flex-shrink: 1;
  min-width: 0;
}

/* The room the columns with a width leave. Columns that take it share it equally. */
.smbk-table__column--width-rest {
  width: auto;
}

/* A share of the table's width, so the column grows and narrows with the table. */
.smbk-table__column--width-share-5 {
  width: 5%;
}

.smbk-table__column--width-share-10 {
  width: 10%;
}

.smbk-table__column--width-share-15 {
  width: 15%;
}

.smbk-table__column--width-share-20 {
  width: 20%;
}

.smbk-table__column--width-share-25 {
  width: 25%;
}

.smbk-table__column--width-share-30 {
  width: 30%;
}

.smbk-table__column--width-share-35 {
  width: 35%;
}

.smbk-table__column--width-share-40 {
  width: 40%;
}

.smbk-table__column--width-share-45 {
  width: 45%;
}

.smbk-table__column--width-share-50 {
  width: 50%;
}

.smbk-table__column--width-share-55 {
  width: 55%;
}

.smbk-table__column--width-share-60 {
  width: 60%;
}

.smbk-table__column--width-share-65 {
  width: 65%;
}

.smbk-table__column--width-share-70 {
  width: 70%;
}

.smbk-table__column--width-share-75 {
  width: 75%;
}

.smbk-table__column--width-share-80 {
  width: 80%;
}

.smbk-table__column--width-share-85 {
  width: 85%;
}

.smbk-table__column--width-share-90 {
  width: 90%;
}

.smbk-table__column--width-share-95 {
  width: 95%;
}

/* A size token: the column keeps this width whatever the table's. */
.smbk-table__column--width-size-40 {
  width: var(--smbk-size-40);
}

.smbk-table__column--width-size-48 {
  width: var(--smbk-size-48);
}

.smbk-table__column--width-size-56 {
  width: var(--smbk-size-56);
}

.smbk-table__column--width-size-64 {
  width: var(--smbk-size-64);
}

.smbk-table__column--width-size-72 {
  width: var(--smbk-size-72);
}

.smbk-table__column--width-size-80 {
  width: var(--smbk-size-80);
}

.smbk-table__column--width-size-88 {
  width: var(--smbk-size-88);
}

.smbk-table__column--width-size-96 {
  width: var(--smbk-size-96);
}

.smbk-table__column--width-size-width-240 {
  width: var(--smbk-size-width-240);
}

.smbk-table__column--width-size-width-320 {
  width: var(--smbk-size-width-320);
}

.smbk-table__column--width-size-width-400 {
  width: var(--smbk-size-width-400);
}

.smbk-table__column--width-size-width-480 {
  width: var(--smbk-size-width-480);
}

/* How narrow the table gets before its frame scrolls it sideways, in place of the least width above; none lets it
   narrow with its frame. */
.smbk-table__table--min-width-none {
  min-width: 0;
}

.smbk-table__table--min-width-size-width-320 {
  min-width: var(--smbk-size-width-320);
}

.smbk-table__table--min-width-size-width-400 {
  min-width: var(--smbk-size-width-400);
}

.smbk-table__table--min-width-size-width-480 {
  min-width: var(--smbk-size-width-480);
}

.smbk-table__table--min-width-size-width-640 {
  min-width: var(--smbk-size-width-640);
}

.smbk-table__table--min-width-size-width-720 {
  min-width: var(--smbk-size-width-720);
}

.smbk-table__table--min-width-size-width-960 {
  min-width: var(--smbk-size-width-960);
}

/* ---- a long table: the heading row stays in view ----
   Two forms. In a frame of its own (sticky-header), the consumer limits the frame's height; the frame then scrolls
   both ways, the headings stick to its top and the foot to its foot. In the page (page-sticky-header), the frame keeps
   no height and scrolls only sideways, and the page's own scroll moves the real heading cells down to stay in view, and
   the foot up to the window's foot, until the table ends (below). A collapsed border stays where it was drawn, so in
   both a heading that sticks or moves carries its rule as a line of its own. */

/* The heading can be pressed anywhere down the table, and a browser anchoring a scroll to a row would follow the row the
   sort moved. Nothing in either form's frame anchors a scroll, so the reader stays where they are. */
.smbk-table--sticky-header {
  overflow-y: auto;
  isolation: isolate;
  overflow-anchor: none;
  /* What the frame scrolls into view (a focused cell, a button) lands clear of the heading and the foot over the rows. */
  scroll-padding-block-start: var(--smbk-size-control-md);
}

.smbk-table--sticky-header:has(> .smbk-table__footer) {
  scroll-padding-block-end: var(--smbk-table-foot-height, var(--smbk-size-control-lg));
}

/* A frame the keyboard reaches: one that scrolls up and down, or a table too wide for it (the wrapper makes it a region then). */
.smbk-table:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
}

.smbk-table--sticky-header .smbk-table__header {
  position: sticky;
  top: 0;
  z-index: var(--smbk-layer-raised);
  border-bottom-color: transparent;
  background: var(--smbk-color-surface-default);
}

/* The rule under a sticking heading, and the hairline between headings, drawn by the heading itself. The rule's part
   also lays the heading's surface again under its words, a hairline past its top edge and past its sides. A cell paints
   nothing in its share of a collapsed border, so without it the rows would show between two headings and the rule would
   break there; and a heading that moves with the scroll can land a device pixel under its place when the table sits
   between two pixels, so they would show along its top too. It stops at the table's two ends, since a part past them
   would give the frame something to scroll. */
:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset-block: calc(-1 * var(--smbk-border-width-hairline)) 0;
  inset-inline: 0;
  border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-control);
  background: inherit;
}

:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header:not(:first-child)::after {
  inset-inline-start: calc(-1 * var(--smbk-border-width-hairline));
}

:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header:not(:last-child)::after {
  inset-inline-end: calc(-1 * var(--smbk-border-width-hairline));
}

:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header + .smbk-table__header {
  border-left-color: transparent;
}

:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header + .smbk-table__header::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: var(--smbk-border-width-hairline);
  left: 0;
  width: var(--smbk-border-width-hairline);
  background: var(--smbk-color-border-subtle);
}

/* The foot stays at the frame's foot while the rows scroll under it, on the heading row's floor. Sideways it scrolls
   with the table, as in every table, so a foot wider than a narrow frame can still be scrolled to its end. */
.smbk-table--sticky-header .smbk-table__footer {
  position: sticky;
  bottom: 0;
  z-index: var(--smbk-layer-raised);
  background: var(--smbk-color-surface-default);
}

/* ---- a long table in the page: the heading row and the foot follow the page's scroll ----
   The frame scrolls sideways only; it never scrolls downwards, so the wheel and the finger always move the page. The
   heading cells move down by the page's scroll to stay just under the room the page keeps clear at the top of what
   scrolls it (its scroll padding, which the system's app bar sets while it sticks), and stop where the table ends;
   the foot, the scroll hint and the scrollbar's sideways track move up to the window's foot, never above the heading
   row's own place. The wrapper's script writes how far they may move (--smbk-table-travel); where the browser links
   an animation to a scroll, it moves them as it scrolls, and elsewhere the script does. Sideways the heading cells
   scroll with their columns, as they are the real cells; a held first column stays held. Without the script nothing
   moves, and the table reads as a plain long one. */

.smbk-table--page-sticky-header {
  overflow-y: hidden;
  isolation: isolate;
  overflow-anchor: none;
}

.smbk-table--page-sticky-header .smbk-table__header {
  position: relative;
  z-index: var(--smbk-layer-raised);
  border-bottom-color: transparent;
  background: var(--smbk-color-surface-default);
}

.smbk-table--page-sticky-header .smbk-table__footer {
  position: relative;
  z-index: var(--smbk-layer-raised);
  background: var(--smbk-color-surface-default);
}

/* A table's height and the text above it often leave the frame's foot on part of a pixel, and a layer moved by part of
   a pixel draws its edge across two rows of pixels, with the rows showing there. So the script writes the part of a
   pixel that lands the foot on a whole one (--smbk-table-foot-snap), to the nearest, and what follows the table moves
   by that much. The heading's move starts later or sooner by a part of its own (below). */
.smbk-table--page-sticky-header > .smbk-table__table {
  margin-block-end: var(--smbk-table-foot-snap, 0px);
}

/* What the page scrolls into view inside the rows (a focused cell, a button) lands clear of the heading and the foot. */
.smbk-table--page-sticky-header .smbk-table__table > tbody * {
  scroll-margin-block: var(--smbk-size-control-md) var(--smbk-size-control-lg);
}

/* While the system's app bar that hides on scroll has slid away, the room it kept at the window's top is free, and
   the heading rises into it. */
:root:has(.smbk-app-bar--scrolled-away) .smbk-table--page-sticky-header {
  --smbk-table-room-top: 0px;
}

@supports (animation-timeline: view()) {
  /* The frame's place in what scrolls it, inside the room kept clear at its top and foot. */
  .smbk-table--page-sticky-header {
    view-timeline-name: --smbk-table-page;
    view-timeline-axis: block;
    view-timeline-inset: var(--smbk-table-room-top, auto) auto;
  }

  /* Down by as much as the frame has passed that top, from its edge, to the table's end. The text above a table often
     leaves the frame on part of a pixel, and a heading moved by part of a pixel draws its rule across two rows of pixels,
     with the rows showing there. So the move starts later or sooner by the part of a pixel that makes it a whole one
     (--smbk-table-top-snap), which the script writes. */
  .smbk-table--page-sticky-header .smbk-table__header {
    animation-name: smbk-table-heading;
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: --smbk-table-page;
    animation-range:
      exit-crossing calc(var(--smbk-border-width-hairline) + var(--smbk-table-top-snap, 0px))
      exit-crossing calc(var(--smbk-border-width-hairline) + var(--smbk-table-top-snap, 0px) + var(--smbk-table-travel, 0px));
  }

  /* Up by as much as the frame runs past the foot, from its edge, as far as the heading row's place. */
  .smbk-table--page-sticky-header > :is(.smbk-table__scroll-hint, .smbk-table__footer),
  .smbk-table--page-sticky-header > .smbk-scrollbar .smbk-scrollbar__track--x {
    animation-name: smbk-table-foot;
    animation-duration: auto;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: --smbk-table-page;
    animation-range:
      entry-crossing calc(100% - var(--smbk-border-width-hairline) - var(--smbk-table-travel, 0px))
      entry-crossing calc(100% - var(--smbk-border-width-hairline));
  }
}

@keyframes smbk-table-heading {
  from {
    translate: 0 0;
  }

  to {
    translate: 0 var(--smbk-table-travel, 0px);
  }
}

@keyframes smbk-table-foot {
  from {
    translate: 0 calc(-1 * var(--smbk-table-travel, 0px));
  }

  to {
    translate: 0 0;
  }
}

/* ---- a wide table: the first column stays in view ----
   The pinned cells are painted on the page canvas, with the row's hover laid over it. They move only sideways, along
   the rules the table draws across each row, so those rules show through them, on the same pixels as in every other
   cell; a copy of its own in a pinned cell draws a pixel off them in every browser. */

.smbk-table--pin-first {
  isolation: isolate;
}

.smbk-table--pin-first .smbk-table__header:first-child,
.smbk-table--pin-first .smbk-table__cell:first-child {
  position: sticky;
  left: 0;
}

.smbk-table--pin-first .smbk-table__header:first-child {
  z-index: var(--smbk-layer-sticky);
  background: var(--smbk-color-surface-default);
}

/* Raised, so what a later cell positions (a bar's track, a meter) passes under the held names rather than over them;
   the first heading stays above it, and the scroll hint, coming after it, over it. */
.smbk-table--pin-first .smbk-table__cell:first-child {
  z-index: var(--smbk-layer-raised);
  background: var(--smbk-color-surface-canvas);
}

@media (hover: hover) {
  .smbk-table--pin-first .smbk-table__row:hover .smbk-table__cell:first-child {
    background: linear-gradient(var(--smbk-color-surface-hover), var(--smbk-color-surface-hover)), var(--smbk-color-surface-canvas);
  }
}

.smbk-table--pin-first .smbk-table__row--selected .smbk-table__cell:first-child,
.smbk-table--pin-first .smbk-table__row--current .smbk-table__cell:first-child,
.smbk-table--pin-first .smbk-table__row--selected:hover .smbk-table__cell:first-child,
.smbk-table--pin-first .smbk-table__row--current:hover .smbk-table__cell:first-child {
  background: var(--smbk-color-action-default-background-pressed);
}

.smbk-table--pin-first .smbk-table__row--highlighted .smbk-table__cell:first-child {
  background: linear-gradient(var(--smbk-color-feedback-info-surface-subtle), var(--smbk-color-feedback-info-surface-subtle)), var(--smbk-color-surface-canvas);
}

/* A pinned row's brand edge, in a held cell: a strip of its own along the cell's start side, inside its edges, so its
   row's rules cross it as everywhere else. The inset shadow the edge is in other tables, Firefox draws along a held
   cell's foot as well, over its row's rule. */
.smbk-table--pin-first .smbk-table__row--pinned > .smbk-table__cell:first-child {
  box-shadow: none;
}

.smbk-table--pin-first .smbk-table__row--pinned > .smbk-table__cell:first-child::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  inset-inline-start: 0;
  width: var(--smbk-border-width-accent);
  background: var(--smbk-color-border-brand);
  pointer-events: none;
}

/* A group heading spans the table: its label sticks at the start while the columns scroll sideways. */
.smbk-table--pin-first .smbk-table__group-label {
  position: sticky;
  left: var(--smbk-space-inset-lg);
}

/* The pinned column's right edge, so the columns sliding under it read as passing behind. */
.smbk-table--pin-first .smbk-table__header:first-child::before,
.smbk-table--pin-first .smbk-table__cell:first-child::before {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--smbk-border-width-hairline);
  background: var(--smbk-color-border-subtle);
}

/* In the long forms the heading's rule runs on under its edge, as under the hairlines between headings. */
:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header).smbk-table--pin-first .smbk-table__header:first-child::before {
  bottom: var(--smbk-border-width-hairline);
}

/* ---- the filled look: the framed table with a calmer heading ----
   The frame, the rows, their height and their states stay as framed draws them. The heading row and the foot sit on
   the neutral fill, with no hairlines between headings; a group row is sunken and one small control high; the room at
   a row's ends and between its columns is the same. The fills are see-through, so a table takes them on any surface.
   These rules come after the long and pinned forms, which this look takes, because they replace what those paint. */

.smbk-table--look-filled .smbk-table__header-row {
  background: none;
}

.smbk-table--look-filled .smbk-table__header {
  background: var(--smbk-color-surface-neutral-subtle);
}

.smbk-table--look-filled .smbk-table__header + .smbk-table__header {
  border-left: 0;
}

.smbk-table--look-filled .smbk-table__header + .smbk-table__header::before {
  content: none;
}

/* The fill marks the heading row and the foot, so the rule along each is the rows' own, not a stronger one. A heading
   that sticks or moves carries its rule as a line of its own, so its border stays clear. */
.smbk-table--look-filled:not(.smbk-table--sticky-header, .smbk-table--page-sticky-header) .smbk-table__header {
  border-bottom-color: var(--smbk-color-border-subtle);
}

.smbk-table--look-filled .smbk-table__header::after {
  border-bottom-color: var(--smbk-color-border-subtle);
}

/* A collapsed table adds its rule's half to what a cell's content and padding take, so the padding stays well inside
   the row's height and the edge is drawn inside it, at compact too. */
.smbk-table--look-filled .smbk-table__header {
  padding-block: var(--smbk-space-inset-xs);
}

/* Half the room between columns on each side of a cell, and the row's own room at its two ends. Each side keeps some,
   so a held first column's words stay clear of its edge. */
.smbk-table--look-filled :is(.smbk-table__header, .smbk-table__cell) {
  padding-inline: calc(var(--smbk-space-inline-xl) / 2);
}

.smbk-table--look-filled :is(.smbk-table__header, .smbk-table__cell):first-child {
  padding-inline-start: var(--smbk-space-inset-xl);
}

.smbk-table--look-filled :is(.smbk-table__header, .smbk-table__cell):last-child {
  padding-inline-end: var(--smbk-space-inset-xl);
}

/* What runs across every column starts where the first column does. */
.smbk-table--look-filled .smbk-table__detail {
  padding-inline: var(--smbk-space-inset-xl);
}

/* The fill is on the heading cell, not the row, so a group row keeps it under the rule that takes a row's hover fill
   away. Its line sits centered in the small control's height, the edge inside it, and a label that wraps keeps that
   room over and under. */
.smbk-table--look-filled .smbk-table__group {
  height: var(--smbk-size-control-sm);
  padding: calc((var(--smbk-size-control-sm) - 1lh) / 2 - var(--smbk-border-width-hairline)) var(--smbk-space-inset-xl);
  background: var(--smbk-color-surface-sunken);
}

/* The foot is at least one small control high, and grows with what it holds: two paginations are taller, and on a
   phone they wrap. So nothing in it runs past the frame, it stays where it is while a wide table scrolls sideways, its
   fill under the whole of what shows; a foot scrolling with the table would leave the table's far end without one. */
.smbk-table--look-filled .smbk-table__footer {
  position: sticky;
  inset-inline: 0;
  flex-wrap: wrap;
  row-gap: var(--smbk-space-stack-xs);
  min-height: var(--smbk-size-control-sm);
  padding: var(--smbk-space-inset-2xs) var(--smbk-space-inset-xl);
  border-top-color: var(--smbk-color-border-subtle);
  background: var(--smbk-color-surface-neutral-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

/* The neutral fill is see-through, so what the rows pass under takes a floor beneath it: the page canvas, as a held
   column's cells do, so a stuck heading looks as it does at rest on the canvas. */
.smbk-table--look-filled:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) :is(.smbk-table__header, .smbk-table__footer),
.smbk-table--look-filled:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header)[data-scroll-hint] .smbk-table__scroll-hint,
.smbk-table--look-filled.smbk-table--pin-first .smbk-table__header:first-child {
  background: linear-gradient(var(--smbk-color-surface-neutral-subtle), var(--smbk-color-surface-neutral-subtle)), var(--smbk-color-surface-canvas);
}

.smbk-table--look-filled.smbk-table--pin-first .smbk-table__group-label {
  left: var(--smbk-space-inset-xl);
}

/* The arrow of a column not sorted waits for the reader: on a device that hovers, over its heading, and at keyboard
   focus. Unseen, it keeps its room, so the heading does not move, and the button stays where Tab reaches it, with its
   name. On a touch screen it shows once the column is sorted. */
.smbk-table--look-filled .smbk-table__header:not(.smbk-table__header--active) .smbk-table__sort > .smbk-table__header-icon {
  opacity: 0;
}

.smbk-table--look-filled .smbk-table__header:not(.smbk-table__header--active) .smbk-table__sort:focus-visible > .smbk-table__header-icon {
  opacity: 1;
}

@media (hover: hover) {
  .smbk-table--look-filled .smbk-table__header:not(.smbk-table__header--active):hover .smbk-table__sort > .smbk-table__header-icon {
    opacity: 1;
  }
}

/* ---- the scroll hint: where the table runs past its frame and the browser hides its own scrollbar until
   the frame scrolls (a phone, a trackpad), the script marks the frame and a thin bar along its foot shows
   that there is more sideways and how much of it is in view (the wrapper sizes and moves the thumb as the
   frame scrolls). It holds the frame's visible edges while the table scrolls under it, and its foot in a
   long table, on the foot where there is one; in the page, it comes up to the window's foot with the foot.
   Decoration: hidden from assistive technology, and it takes no press. ---- */

.smbk-table__scroll-hint {
  display: none;
}

.smbk-table[data-scroll-hint] .smbk-table__scroll-hint {
  position: sticky;
  left: 0;
  bottom: 0;
  z-index: var(--smbk-layer-raised);
  display: block;
  padding: var(--smbk-space-inset-xs) var(--smbk-space-inset-sm);
  pointer-events: none;
}

/* While the system's scrollbar shows over the frame (scrollbar.ts), in the same strip, the hint's bar fades out of
   its way, so there is never a second bar. The board's hint does the same. */
.smbk-table[data-scroll-hint]:has(> .smbk-scrollbar[data-shown]) .smbk-table__scroll-track {
  opacity: 0;
}

/* In a long table the rows scroll under the hint, so it takes the heading row's floor. */
:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header)[data-scroll-hint] .smbk-table__scroll-hint {
  background: var(--smbk-color-surface-default);
}

/* In a frame of its own the hint sits on the foot, which sticks to the frame's foot under it (the wrapper writes the
   foot's height). */
.smbk-table--sticky-header[data-scroll-hint] .smbk-table__scroll-hint {
  bottom: var(--smbk-table-foot-height, 0px);
}

.smbk-table__scroll-track {
  position: relative;
  transition: opacity var(--smbk-motion-feedback-duration) var(--smbk-motion-feedback-easing);
  height: var(--smbk-size-4);
  overflow: hidden;
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-border-subtle);
}

.smbk-table__scroll-thumb {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: var(--smbk-radius-full);
  background: var(--smbk-color-border-strong);
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  /* Everything in the row, links and the components in its cells (a tag, an icon) included:
     forced colors gives a part that names its own color the palette's text color, not the row's. */
  .smbk-table__row--disabled,
  .smbk-table__row--disabled * {
    color: GrayText;
  }

  /* The brand edge is a shadow, which forced colors drops: a border in the user's text color takes its place. */
  .smbk-table__row--pinned > .smbk-table__cell:first-child {
    border-inline-start: var(--smbk-border-width-accent) solid CanvasText;
  }

  /* A floor drawn as a picture stays in this mode, and the user's text color may not read on it. */
  .smbk-table--look-filled:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header) :is(.smbk-table__header, .smbk-table__footer),
  .smbk-table--look-filled:is(.smbk-table--sticky-header, .smbk-table--page-sticky-header)[data-scroll-hint] .smbk-table__scroll-hint,
  .smbk-table--look-filled.smbk-table--pin-first .smbk-table__header:first-child {
    background-image: none;
  }
}

/* ---- touch: the small buttons and choose boxes take invisible press areas, each within its own row ----
   On a touch screen an invisible area, centred on the sort, copy and caret buttons (the filter and info buttons and a
   choose box take their own), takes the press out to the comfortable target where it falls short of it, but never
   past its own row, so the rows above and below keep theirs. Inside a cell, a button or a choose box beside another
   reaches only halfway into the gap between them. The lines a sticking heading draws for itself take
   no press, so they never cut the edge off an area, and a pinned column keeps what the frame scrolls to out from under
   it. What is drawn and every place stay as they are. */

@media (pointer: coarse) {
  .smbk-table__sort,
  .smbk-table__copy,
  .smbk-table__expander {
    position: relative;
  }

  .smbk-table__sort::after,
  .smbk-table__copy::after,
  .smbk-table__expander::after {
    content: '';
    position: absolute;
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  /* Within its row: never taller than the row, a medium control. */
  :is(.smbk-table__header, .smbk-table__cell) :is(.smbk-table__sort, .smbk-table__copy, .smbk-table__expander, .smbk-btn, .smbk-checkbox)::after {
    inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (100% - var(--smbk-size-control-md)) / 2);
  }

  /* Within a flat row: one line of the body text and the small inset above and below it, or a control and that inset
     where the control is taller than the line. In the heading every control is held to the line, and the copy and
     caret buttons give back the room they take past it, so the line alone sets their row. */
  .smbk-table--look-flat :is(.smbk-table__header, .smbk-table__cell) :is(.smbk-table__sort, .smbk-table__copy, .smbk-table__expander, .smbk-btn, .smbk-checkbox)::after {
    inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), min(-1 * var(--smbk-space-inset-sm), (100% - var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height)) / 2 - var(--smbk-space-inset-sm)));
  }

  .smbk-table--look-flat .smbk-table__header :is(.smbk-table__sort, .smbk-btn, .smbk-checkbox)::after,
  .smbk-table--look-flat .smbk-table__cell :is(.smbk-table__copy, .smbk-table__expander)::after {
    inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (100% - var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height)) / 2 - var(--smbk-space-inset-sm));
  }

  /* A row grown to hold a value over its description is taller than the comfortable target in either density, so its
     controls take the whole target and still stay inside it. */
  .smbk-table__row:has(.smbk-table__stack) :is(.smbk-table__sort, .smbk-table__copy, .smbk-table__expander, .smbk-btn, .smbk-checkbox)::after {
    inset-block: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  /* In a filled table, a control at the top of a row a prose cell holds has the prose's lines under it, so it takes the
     whole target downward. How many lines the prose has no stylesheet can tell: on a row of one line it reaches a few
     pixels past the row's foot, where the next row's own controls stay on top of it. */
  .smbk-table--look-filled .smbk-table__row:has(> .smbk-table__cell--type-prose) :is(.smbk-table__copy, .smbk-table__expander, .smbk-btn, .smbk-checkbox)::after {
    inset-block-end: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  /* In a row aligned to the top, a control on the first line has the row under it, however tall the row: it takes the
     whole target downward and stops at the row's top. On a row of one line it reaches a few pixels past the row's foot,
     as in a filled row a prose cell holds. */
  .smbk-table--align-top:not(.smbk-table--look-flat) .smbk-table__row :is(.smbk-table__copy, .smbk-table__expander, .smbk-checkbox, .smbk-table__cell > .smbk-table__content > .smbk-btn)::after {
    inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (100% - var(--smbk-size-control-md)) / 2);
    inset-block-end: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  /* Beside each other in a cell: halfway into the cell's gap. A choose box beside words alone (a heading that does not
     sort, a value with no link or button) keeps its whole area. */
  .smbk-table__header-cell > .smbk-table__sort:has(~ :is(.smbk-btn, :has(.smbk-btn)))::after,
  :is(.smbk-table__header-cell, .smbk-table__content) > .smbk-checkbox:has(+ :is(a, button), + * :is(a, button))::after,
  .smbk-table__content > .smbk-btn:has(+ .smbk-btn)::after {
    inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
  }

  .smbk-table__header-cell > .smbk-table__sort ~ .smbk-btn::after,
  .smbk-table__header-cell > .smbk-table__sort ~ * .smbk-btn::after,
  .smbk-table__header-cell > .smbk-checkbox ~ .smbk-table__sort::after,
  .smbk-table__content > .smbk-btn + .smbk-btn::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
  }

  /* A caret beside a row's choose box, which the wrapper draws first in the content after the lead: each halfway. */
  .smbk-table__start > .smbk-table__lead:has(+ .smbk-checkbox) > .smbk-table__expander::after,
  .smbk-table__start > .smbk-table__lead:has(+ .smbk-table__content > .smbk-checkbox:first-child) > .smbk-table__expander::after {
    inset-inline-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-lg) + (var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2) / -2);
  }

  .smbk-table__start > .smbk-table__lead:has(> .smbk-table__expander) + .smbk-table__content > .smbk-checkbox:first-child::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-inline-lg) + (var(--smbk-size-icon-sm) - var(--smbk-size-target-min)) / 2) / -2);
  }

  /* A pinned first column keeps what the frame scrolls into view out from under it, by its width, which the wrapper
     writes; without the wrapper the frame scrolls as it always does. */
  .smbk-table--pin-first {
    scroll-padding-inline-start: var(--smbk-table-pinned-width, auto);
  }

  .smbk-table__header::before,
  .smbk-table__header::after,
  .smbk-table__cell::before,
  .smbk-table__cell::after {
    pointer-events: none;
  }
}

/* ---- a tall heading: a column headed by a block ----
   A column that asks for it holds a block in its heading, a few lines and buttons, and the cell grows to hold it. The
   table lays the heading row out as any row, so the row is as high as its tallest heading and the line under it runs
   under that one. A row with a tall heading starts every heading at the top, each first line where a one-line heading's
   sits; the columns that did not ask keep their one line. The sort button cannot hold a block, which may hold buttons of
   its own, so here it is the arrow alone, after the block, or before it in a column lined up at the end. */

.smbk-table__header--tall {
  white-space: normal;
}

.smbk-table__header--tall .smbk-table__header-cell {
  align-items: flex-start;
  height: auto;
  min-height: var(--smbk-size-mark-md);
}

.smbk-table__header-row:has(> .smbk-table__header--tall) > .smbk-table__header {
  padding-block: calc((var(--smbk-size-control-md) - var(--smbk-size-mark-md)) / 2);
  vertical-align: top;
}

.smbk-table--look-flat .smbk-table__header-row:has(> .smbk-table__header--tall) > .smbk-table__header {
  padding-block: var(--smbk-space-inset-sm);
}

.smbk-table--look-flat .smbk-table__header--tall .smbk-table__header-cell {
  min-height: calc(var(--smbk-typography-body-sm-font-size) * var(--smbk-typography-body-sm-line-height));
}

/* A column keeps a width its block can be read at while a table too wide for its frame narrows; a column with a width
   of its own sets it. The block's lines stack, and it fills the heading, so a row of buttons in it can reach the
   column's end. */
.smbk-table__header--tall .smbk-table__header-cell {
  min-width: var(--smbk-size-width-240);
}

.smbk-table__table--columns-fixed .smbk-table__header--tall .smbk-table__header-cell {
  min-width: 0;
}

.smbk-table__header-block {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-stack-xs);
  min-width: 0;
  color: var(--smbk-color-text-default);
}

/* In a column lined up at the end, its lines and buttons go there too, the arrow close before them. */
.smbk-table__header--align-end .smbk-table__header-block {
  flex-grow: 0;
  align-items: flex-end;
}

/* The arrow alone: the least a press needs both ways, on the block's first line. */
.smbk-table__header--tall .smbk-table__sort {
  flex-shrink: 0;
  justify-content: center;
  min-width: var(--smbk-size-target-min);
}

@media (hover: hover) {
  .smbk-table__header--tall .smbk-table__sort:hover > .smbk-table__header-icon {
    color: var(--smbk-color-text-default);
  }
}

/* It is all there is to press, so in the filled look it does not wait for a reader to go to it. */
.smbk-table--look-filled .smbk-table__header--tall:not(.smbk-table__header--active) .smbk-table__sort > .smbk-table__header-icon {
  opacity: 1;
}

/* A row's press areas stop at a medium control's height, which a tall heading has passed: its lines and the room at its
   foot lie under its controls, so on a touch screen each takes the whole target downward. */
@media (pointer: coarse) {
  .smbk-table .smbk-table__header--tall :is(.smbk-table__sort, .smbk-btn)::after {
    inset-block-end: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }
}

/* In the long forms, what is scrolled into view lands clear of the heading as high as it stands: the wrapper writes its
   height when a heading can be more than one line, a tall heading or a long one in a column with a width. */
.smbk-table--sticky-header {
  scroll-padding-block-start: var(--smbk-table-head-height, var(--smbk-size-control-md));
}

.smbk-table--page-sticky-header .smbk-table__table > tbody * {
  scroll-margin-block-start: var(--smbk-table-head-height, var(--smbk-size-control-md));
}

/* A heading that would take more than half of what shows would cover the rows it heads, so the wrapper marks the frame
   and the heading stays in its place at the table's top, scrolling away with the rows, and leaves them all the room. A
   held first column stays held sideways. In the page the script writes its move on the cells themselves, which only an
   important rule stands above. */
.smbk-table--sticky-header[data-heading-scrolls] {
  scroll-padding-block-start: 0;
}

.smbk-table--sticky-header[data-heading-scrolls] .smbk-table__header {
  top: auto;
}

.smbk-table--page-sticky-header[data-heading-scrolls] .smbk-table__header {
  animation-name: none;
  translate: none !important;
}

.smbk-table--page-sticky-header[data-heading-scrolls] .smbk-table__table > tbody * {
  scroll-margin-block-start: 0;
}

/* ---- a row that can be pressed as a whole ----
   Off unless a row is given a link or an action. The row stays a plain table row: one real link or button of its own,
   with no words to show and a name for screen readers, lies over the whole row, so a press anywhere on it is a press
   on that link or button, and a link opens in a new tab from anywhere on the row as any link does. Whatever a person
   can press in a cell lies over that link or button in turn, so a press there does its own job and never reaches the
   row's. It lies over what a cell only shows, a bar or a picture included, so no part of a row takes a press and does
   nothing. A held first column is a layer that nothing outside it can reach into: there the row's link or button is
   the first thing in the second cell, and the held cell holds an unnamed copy of it that Tab and screen readers leave
   out. A control passing under the held column stays out of reach, as in every held table. */

.smbk-table:has(.smbk-table__row--pressable) {
  isolation: isolate;
}

.smbk-table__row--pressable {
  position: relative;
}

.smbk-table__row-press {
  position: absolute;
  inset: 0;
  z-index: 1;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  appearance: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* No weight, so a control's own place in its stylesheet stands; the row's link or button, a class, keeps its own. */
:where(.smbk-table__row--pressable > .smbk-table__cell) :where(a[href], button, input, select, textarea, label, summary, [tabindex], [contenteditable], [aria-disabled='true']) {
  position: relative;
  z-index: 2;
}

/* A control that cannot be pressed now keeps the press and does nothing with it. Left to take no pointer, as it does
   elsewhere, it would pass the press down to the row. Its disabled look holds under the pointer. */
.smbk-table__row--pressable > .smbk-table__cell :is(:disabled, [aria-disabled='true']) {
  pointer-events: auto;
  cursor: not-allowed;
}

/* The ring runs round the row, over a held cell too, and lies inside it, since the frame would clip it. The link or
   button sits under what a held cell paints, so the cell holding it draws the ring, over the whole row as the link or
   button lies. The row itself cannot: anything it draws, placed absolutely or not, gets a table cell of its own from
   the browser, which is one more column, and in a table with column widths takes room from the columns. A held column
   alone holds the link or button itself, where the ring stands in for a pinned row's edge while it shows. */
.smbk-table__row-press:focus-visible {
  outline: 0;
}

.smbk-table__row--pressable > .smbk-table__cell:has(> .smbk-table__row-press:focus-visible)::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--smbk-layer-raised);
  width: auto;
  background: none;
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: calc(-1 * var(--smbk-focus-ring-width));
  pointer-events: none;
}

/* While it is pressed it takes the fill it takes while what it opens is open (the current row). On a touch screen, with
   no hover, this is the sign that the press landed. */
.smbk-table__row--pressable:has(> .smbk-table__cell > .smbk-table__row-press:active),
.smbk-table--pin-first .smbk-table__row--pressable:has(> .smbk-table__cell > .smbk-table__row-press:active) .smbk-table__cell:first-child {
  background: var(--smbk-color-action-default-background-pressed);
}
Tokens it reads
Table tokens
Token and CSSValuePreviewFoundation
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.subtle--smbk-color-border-subtlelight: #c4cdd8dark: #2d3237changes with themeColor
radius.container.sm--smbk-radius-container-sm8pxRadius
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
color.surface.default--smbk-color-surface-defaultlight: #f3f5f8dark: #181a1cchanges with themeColor
size.control.md--smbk-size-control-mdcomfortable: 40pxcompact: 32pxchanges with densitySize
space.inset.md--smbk-space-inset-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
space.inset.lg--smbk-space-inset-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
color.border.control--smbk-color-border-control#7c8692Color
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.label-sm--smbk-typography-label-sm-font-family--smbk-typography-label-sm-font-size--smbk-typography-label-sm-font-weight--smbk-typography-label-sm-line-height--smbk-typography-label-sm-letter-spacing--smbk-typography-label-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
size.mark.md--smbk-size-mark-md20pxSize
space.inline.xs--smbk-space-inline-xs4pxSpacing
size.icon.xs--smbk-size-icon-xs12pxSize
size.target.min--smbk-size-target-min24pxSize
size.control.xs--smbk-size-control-xs24pxSize
focus.ring.width--smbk-focus-ring-width2pxFocus
color.border.focus--smbk-color-border-focuslight: #cc0d0ddark: #f34336changes with themeColor
focus.ring.offset--smbk-focus-ring-offset2pxFocus
radius.control.xs--smbk-radius-control-xs2pxRadius
color.surface.hover--smbk-color-surface-hoverlight: #373d4326dark: #606a754dchanges with themeColor
color.action.default.background-pressed--smbk-color-action-default-background-pressedlight: #c4cdd8dark: #4e555echanges with themeColor
color.feedback.info.surface-subtle--smbk-color-feedback-info-surface-subtlelight: #e3ebfedark: #162352changes with themeColor
border-width.accent--smbk-border-width-accent3pxBorder width
color.border.brand--smbk-color-border-brand#cc0d0dColor
color.border.strong--smbk-color-border-stronglight: #9ba5b1dark: #373d43changes with themeColor
space.inset.sm--smbk-space-inset-smcomfortable: 6pxcompact: 4pxchanges with densitySpacing
typography.label-md--smbk-typography-label-md-font-family--smbk-typography-label-md-font-size--smbk-typography-label-md-font-weight--smbk-typography-label-md-line-height--smbk-typography-label-md-letter-spacing--smbk-typography-label-md-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
space.inline.sm--smbk-space-inline-smcomfortable: 6pxcompact: 4pxchanges with densitySpacing
color.surface.sunken--smbk-color-surface-sunkenlight: #373d430ddark: #00000066changes with themeColor
typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.text.disabled--smbk-color-text-disabledlight: #7c8692dark: #606a75changes with themeColor
opacity.disabled--smbk-opacity-disabled0.5Opacity
size.control.sm--smbk-size-control-smcomfortable: 32pxcompact: 28pxchanges with densitySize
size.table.prose-min--smbk-size-table-prose-min240pxSize
size.table.prose-max--smbk-size-table-prose-max480pxSize
size.icon.sm--smbk-size-icon-sm16pxSize
space.inline.lg--smbk-space-inline-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
size.selection.sm--smbk-size-selection-sm16pxSize
space.text.label--smbk-space-text-label0pxSpacing
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.text.subtlest--smbk-color-text-subtlestlight: #606a75dark: #7c8692changes with themeColor
color.text.brand--smbk-color-text-brandlight: #cc0d0ddark: #f34336changes with themeColor
size.80--smbk-size-8080pxSize
size.control.lg--smbk-size-control-lgcomfortable: 48pxcompact: 40pxchanges with densitySize
space.inset.xs--smbk-space-inset-xs4pxSpacing
size.width.480--smbk-size-width-480480pxSize
size.40--smbk-size-4040pxSize
size.48--smbk-size-4848pxSize
size.56--smbk-size-5656pxSize
size.64--smbk-size-6464pxSize
size.72--smbk-size-7272pxSize
size.88--smbk-size-8888pxSize
size.96--smbk-size-9696pxSize
size.width.240--smbk-size-width-240240pxSize
size.width.320--smbk-size-width-320320pxSize
size.width.400--smbk-size-width-400400pxSize
size.width.640--smbk-size-width-640640pxSize
size.width.720--smbk-size-width-720720pxSize
size.width.960--smbk-size-width-960960pxSize
layer.raised--smbk-layer-raised1000Stacking order
layer.sticky--smbk-layer-sticky1100Stacking order
color.surface.canvas--smbk-color-surface-canvaslight: #e7ebf0dark: #111213changes with themeColor
color.surface.neutral-subtle--smbk-color-surface-neutral-subtlelight: #373d431adark: #606a7533changes with themeColor
space.inline.xl--smbk-space-inline-xlcomfortable: 16pxcompact: 12pxchanges with densitySpacing
space.inset.xl--smbk-space-inset-xlcomfortable: 16pxcompact: 12pxchanges with densitySpacing
space.stack.xs--smbk-space-stack-xs4pxSpacing
space.inset.2xs--smbk-space-inset-2xs2pxSpacing
motion.feedback.duration--smbk-motion-feedback-duration150msMotion
motion.feedback.easing--smbk-motion-feedback-easingcubic-bezier(0.16, 1, 0.3, 1)Motion
size.4--smbk-size-44pxSize
radius.full--smbk-radius-full9999pxRadius
size.target.comfortable--smbk-size-target-comfortable44pxSize
Manifest
table.manifest.json
{
  "name": "table",
  "block": "smbk-table",
  "summary": "Rows of data under headings that sort, filter and explain, with rows that open, pin or group, optional selection and a foot for the pagination.",
  "adapt": "css",
  "axes": {
    "look": {
      "values": [
        "framed",
        "flat",
        "filled"
      ],
      "default": "framed",
      "description": "Framed: a bordered, rounded frame, the heading row on its own surface, room at the sides. Flat, for a small table inside a card: no frame, no heading surface, a rule over every row, no room at the sides, so the table's words line up with the card's other lines, and rows one line of text high. A flat table takes none of sticky-header, page-sticky-header and pin-first. Filled, for a calmer table on the page: the framed frame and rows, the heading row and the foot on a neutral fill, group rows sunken and low, no lines between headings, the same room at a row's ends and between its columns, and the arrow of a column not sorted only on hover and keyboard focus."
    }
  },
  "flags": {
    "sticky-header": {
      "description": "A long table in a frame of its own: the heading row stays at the frame's top and the foot at its foot while the rows scroll inside it. The consumer limits the frame's height; the frame is then a named region the keyboard can reach. Framed and filled looks."
    },
    "page-sticky-header": {
      "description": "A long table in the page: the frame keeps no height of its own, and the heading row stays in view under the room the page keeps clear at its top while the page scrolls, with the foot at the foot of the window, until the table ends. The table still scrolls sideways, its heading with it. Framed and filled looks."
    },
    "pin-first": {
      "description": "A wide table: the first column stays in view while the table scrolls sideways, so every row keeps its subject. For a table on the page canvas, in the framed and filled looks."
    },
    "align-top": {
      "description": "Cells of very different heights: every cell starts at the top of its row, and its first line sits level with the first line of the others, so the eye reads straight across a tall row. All three looks."
    }
  },
  "parts": [
    "table",
    "caption",
    "column",
    "header-row",
    "header",
    "header-cell",
    "header-text",
    "header-label",
    "header-block",
    "header-icon",
    "sort",
    "filter",
    "filter-count",
    "info",
    "row",
    "row-press",
    "cell",
    "start",
    "lead",
    "expander",
    "pin",
    "content",
    "choose-space",
    "value",
    "value-icon",
    "stack",
    "description",
    "copy",
    "prose",
    "figure",
    "bar",
    "group",
    "group-label",
    "group-count",
    "detail-row",
    "detail",
    "detail-title",
    "footer",
    "scroll-hint",
    "scroll-track",
    "scroll-thumb"
  ],
  "figmaNodeId": "4318:16988"
}