@layer components {
  /* Tables — rows of one kind of record, with a column per field.
   *
   * Only how the cells sit. The surface is .panel.panel--flush in the markup,
   * on the table element itself, so a table doesn't restate the app's one
   * elevation decision and doesn't drift from it either.
   *
   * That flush panel clips to its corner radius, which is what a popover
   * anchored inside a cell runs into: a row-actions menu is clipped by the
   * table rather than overflowing it. Put the menu's popover outside the table,
   * or the table in a .panel that isn't flush. See the tables page in /designs.
   */
  .table {
    --table-header-background: var(--surface-quiet);
    /* How far a full-bleed table's outer columns are inset — see .table--bleed.
       Declared here rather than on the modifier so it is one property to
       override, and off --screen-gutter so a table cannot start on a different
       line from the toolbar above it. */
    --table-padding-block: var(--space-three-quarters);
    --table-padding-inline: var(--space);
    /* The outer columns, where the table meets whatever is holding it. Its own
       property rather than the one above, because those are two different
       measurements that happen to start equal: the inline padding is the space
       between two columns and shrinks when there are a lot of them, while this
       is the space between the first column and an edge and never should. */
    --table-edge-inline: var(--space);

    inline-size: 100%;
    border-collapse: collapse;

    th, td {
      padding: var(--table-padding-block) var(--table-padding-inline);
      text-align: start;
      font-variant-numeric: tabular-nums;
    }

    /* Tinted as well as ruled. On a flush panel the header is the top of the
       surface, and a header that is only a line of small grey capitals over the
       same white as the rows reads as a first row with something wrong with it —
       which is exactly how it read on a screen showing three tables in a
       column. */
    th {
      background: var(--table-header-background);
      font-size: var(--text-x-small);
      text-transform: uppercase;
      letter-spacing: var(--tracking-wide);
      color: var(--color-ink-soft);
      border-block-end: var(--border-faint);
    }

    /* Faint, because these rules sit inside one surface. Drawn at --border
       weight — the weight of the surface's own edge — a five-column table stops
       reading as rows of records and starts reading as a grid. */
    tbody tr:not(:last-child) td {
      border-block-end: var(--border-faint);
    }

    /* A total under the rows it adds up: ruled off and tinted like the header,
       so the two ends frame the records rather than the total reading as one
       more of them. */
    tfoot td {
      border-block-start: var(--border-faint);
      background: var(--table-header-background);
      font-weight: var(--weight-semibold);
    }

    /* Every table gets this, not just a full-bleed one: a first column hard
       against the edge of the card holding it reads as text that has slipped
       out of its box, and the last column's figures end on the corner. */
    :is(th, td):first-child {
      padding-inline-start: var(--table-edge-inline);
    }

    :is(th, td):last-child {
      padding-inline-end: var(--table-edge-inline);
    }

    /* A column of figures, ended rather than started, so they line up under each
       other and a $1,200 cannot be read as a $120 in passing. The header goes with
       them or it labels the wrong edge.
       Nested and qualified by the element on purpose: the th/td rule above is one
       specificity step above a bare class, so a class on its own would lose to it
       and quietly do nothing. */
    th.table__number,
    td.table__number {
      text-align: end;
    }

    /* The figures themselves are monospaced, the label above them is not.
       tabular-nums on the sans gets the digits to one width; a mono face also
       gets the comma, the period and the currency symbol there, which is what
       makes $1,140.00 and $215.00 line up on the decimal rather than merely
       end on the same edge. A column heading is a word and reads as one. */
    td.table__number {
      font-family: var(--font-mono);
    }

    /* The last column, where a row's own actions live.
     *
     * **Deliberately not `display: flex` on the cell**, which is what this was.
     * `display: flex` on a `<td>` takes it out of the table's formatting context
     * — the browser wraps it in an anonymous table-cell — and the column stops
     * being sized with the others: a wide action then carries the whole row past
     * the edge of the panel holding it, with the row rules stopping at the panel
     * and the buttons outside it. Measured on the channel listings table, where
     * the action is "Put on Airbnb" rather than "Edit".
     *
     * So the cell stays a cell and its contents lay out inline. Nested and
     * qualified by the element for the reason .table__number is: the th/td rule
     * above is one specificity step above a bare class, so a class on its own
     * loses to it and the alignment quietly does nothing.
     */
    td.table__actions {
      text-align: end;
      white-space: nowrap;
    }

    td.table__actions > * {
      vertical-align: middle;
    }

    /* button_to renders a <form>, which is block-level and would drop each
       action onto a line of its own. */
    td.table__actions > form {
      display: inline-block;
    }

    td.table__actions > * + * {
      margin-inline-start: var(--space-half);
    }
  }

  /* Cells that hold paragraphs: replies side by side, read across a row. Each
   * starts at the top of its row, so two of different lengths begin on the
   * same line rather than centred against each other. */
  .table--align-top :is(th, td) {
    vertical-align: top;
  }

  /* Eight columns or more.
   *
   * Cell padding is the only thing here that scales with the number of columns:
   * at nine of them the app's ordinary gutter is spent nine times over, and the
   * widest column — a name somebody reads — is the one that pays for it. A
   * third off the inline padding gives most of a column back.
   *
   * Only the inline half. The vertical rhythm is what makes a long list
   * readable, and a dense table is exactly the one being scanned down.
   */
  .table--dense {
    --table-padding-inline: var(--space-half);
  }

  /* A table that is the screen rather than a card on one.
   *
   * On a flush screen there is no card to bring a gutter, so the table runs to
   * both edges of the window and its outer columns take the screen's own inset
   * instead — otherwise the first column starts hard against the glass while
   * the title above it starts an inch in, and the two read as belonging to
   * different pages.
   *
   * One property, because the edge inset is already a thing .table has; this
   * only says how much of it a screen wants. */
  .table--bleed {
    --table-edge-inline: var(--screen-gutter);
  }

  /* Rows that go somewhere.
   *
   * A row of nine columns is read by tracking across it, and at that width the
   * eye loses the line it started on. The tint is the same wash the sidebar
   * uses to say "you are pointing at this", so pointing at a row means one
   * thing everywhere in the app.
   *
   * Opt-in rather than on .table, because a table of figures nobody can click
   * offering to be clicked is a row somebody presses and nothing happens.
   */
  .table--interactive tbody tr:hover td {
    background: var(--surface-hover);
  }

  /* The header stays while the rows go past it.
   *
   * For a table inside .screen__scroll, where the list is long enough to lose
   * track of which column is which. It sticks to the top of whatever scrolls
   * it, so it needs no offset of its own: on a flush screen the toolbar and
   * the filter strip are outside the scroller and nothing is above this.
   *
   * The header is already tinted, which is what makes this safe — rows sliding
   * under a transparent header is the failure this would otherwise have. */
  .table--sticky-head thead th {
    position: sticky;
    inset-block-start: 0;
    z-index: var(--z-sticky-header);
  }

  /* Wider than the screen it is on.
   *
   * By default a table squeezes: it is inline-size 100%, so nine columns in a
   * laptop's width take the space away from whichever column can give it, and
   * the one that can is always the name somebody is reading. This says the
   * opposite — every column keeps the width its content asks for, and the
   * scroller around the table takes up the difference.
   *
   * Which is what makes a column cheap to add: a tenth one pushes the table
   * wider rather than taking a line off every name in it.
   *
   * inline-size: 100% still applies above that floor, so on a wide screen the
   * table fills the window instead of stopping in the middle of it.
   *
   * It needs a scrolling ancestor — .screen__scroll, usually. Inside
   * .panel--flush it would be clipped instead, which is the same corner-radius
   * clipping that cuts off a row-actions menu. See the tables page in /designs.
   */
  .table--wide {
    min-inline-size: max-content;
  }

  /* The first column stays while the rest go past.
   *
   * The companion to --wide: a row scrolled halfway along is a row of figures
   * with nothing saying whose they are. Both bands are opaque because a sticky
   * cell has content passing underneath it, and the heading of the pinned
   * column is in both bands at once — hence its own layer. */
  .table--sticky-lead {
    :is(th, td):first-child {
      position: sticky;
      inset-inline-start: 0;
      z-index: var(--z-sticky-cell);
      background: var(--color-canvas);
    }

    thead th:first-child,
    tfoot td:first-child {
      z-index: var(--z-sticky-corner);
      background: var(--table-header-background);
    }
  }

  /* A column heading that sorts the list.
   *
   * A link rather than a button, because a sorted list is a URL: bookmarkable,
   * reloadable, and the back button puts the order back. It inherits the
   * heading's own type, so a sortable column and a plain one are the same
   * heading with an indicator after it.
   *
   * The whole cell is the target — .table__number ends its column, so a link
   * that only wrapped the words would leave a strip of unclickable header
   * beside them exactly where somebody aims. */
  .table__sort {
    display: flex;
    align-items: center;
    gap: var(--space-quarter);
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-decoration: none;
  }

  th.table__number .table__sort {
    justify-content: flex-end;
  }

  .table__sort:hover {
    color: var(--color-ink);
  }

  /* Quiet until the column is the one being sorted by. Every sortable column
     carries a mark, because a mark that appeared on hover would mean the only
     way to find out which columns sort is to sweep the header with a pointer —
     and a touch screen has no way to do that at all. */
  .table__sort-indicator {
    color: var(--color-ink-faint);
  }

  [aria-sort] .table__sort-indicator {
    color: var(--color-accent);
  }

  [aria-sort] .table__sort {
    color: var(--color-ink);
    font-weight: var(--weight-semibold);
  }

  /* The row that fetches the next page. It stands where the rows it is about to
     be replaced by will, so the scrollbar does not jump when they arrive — which
     is the whole reason a placeholder is a shape rather than a spinner. */
  .table__more td {
    padding-block: var(--space);
  }

}
