Skip to content

feat(table): s2 table migration layout part-1 #3799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

marissahuysentruyt
Copy link
Collaborator

@marissahuysentruyt marissahuysentruyt commented May 20, 2025

Description

This PR is part 1 of the table's migration to S2! Table has been updated to include:

  • new layout tokens (in the table Figma token specs, only tokens for Layout have been addressed)
  • new stories to demonstrate support, including a numerical data table showcasing proper text alignment, and row/cell states
  • removal of the table t-shirt sizes
  • the removal of the dropzone table stories on the docs pages (those are still in the code, but have been tagged !autodocs)
  • inclusion of new visual elements to support avatars and icons, as well as thumbnails
  • new empty and loading state tables
  • CSS reorganization to match the patterns of other components, which includes organizing all custom properties towards the top of the file, putting the forced-colors query at the bottom of the file, consolidating repetitive styles/selectors
  • minimal style updates to address new table styles, like a new row focus indicator, a cell focus indicator, new borders around header cells

NOTE: Tokens for color and typography, variant support for sparklines, and any remaining touch ups (like selected hover/down states) will be done in part 2 of the table migration here: #3818

All styles for the full migration will not be addressed in this PR. Layout changes only!

Jira/Specs

CSS-1178
Figma token specs- layout token column only
CSS-1180- this is a corresponding spike ticket. I used the answers to the questions in that ticket to inform some of my decisions around not exposing certain stories.

How and where has this been tested?

Please tag yourself on the tests you've marked complete to confirm the tests have been run by someone other than the author.

Validation steps

Please review the following table layout-specific styles to ensure the tokens are used as outlined in the tokens spec Figma file:

(@5t3ph)

  • proper corner radius of the cells in the corners of the table (corner-radius is specific to particular corners of particular cells)
  • borders around cells at the edges/outside of the table (width-only)
  • row and cell divider width
  • header row min-height
  • non-header row heights for compact, regular, spacious densities
  • text spacing in header row (top & bottom)
  • text spacing in non-header rows for compact, regular, spacious densities (top & bottom)
  • cell inline padding (content to the edge of a cell or content to the edge of the next cell)
  • text to visuals (thumbnail to text, avatar to text, icons to text, sort icons to text, header cell text to disclosure icon)
  • updated sort icons ("sort," "sort up," and "sort down")
  • please double check the calculated padding for checkboxes to text. There's code comments that describe how the full token is calculated
  • checkbox spacing in the header cells
  • checkbox spacing in non-header cells in compact, regular, spacious densities
  • text alignment for numerical cells (should be end/right-aligned)
  • summary row heights for compact, regular, spacious densities
  • text spacing in summary rows for compact, regular, spacious densities (top & bottom)
  • section header row heights
  • text spacing in section header rows (top & bottom)
  • side focus indicator for row focus
  • corner radius for row focus indicator (it should differ based on if the row has rounded corners, i.e. it's at the bottom of the table)
  • corner radius for cell focus

Additional validation

(@5t3ph)

  • Proofread the documentation. There's been additions that need to be reviewed for grammar and to make sure they make sense.
  • Make sure all tables that cannot be created in the controls are listed in the side nav.
  • The testing grid covers all of the stories in some form or another. We also want to include the various focus indicators for rows and cells, along with selected vs highlighted rows, etc.
  • Double check that the fullscreen dialog looks acceptable still 👍
  • Double check the the WHCM mode at least matches what we see on production.

Regression testing

Validate:

  1. The documentation pages for at least two other components are still loading, including:
  • The pages render correctly, are accessible, and are responsive.
  1. If components have been modified, VRTs have been run on this branch:
  • VRTs have been run and looked at.
  • Any VRT changes have been accepted (by reviewer and/or PR author), or there are no changes.

Screenshots

To-do list

  • I have read the contribution guidelines.
  • I have updated relevant storybook stories and templates.
  • I have tested these changes in Windows High Contrast mode.
  • If my change impacts other components, I have tested to make sure they don't break.
  • If my change impacts documentation, I have updated the documentation accordingly.
  • ✨ This pull request is ready to merge. ✨

@marissahuysentruyt marissahuysentruyt self-assigned this May 20, 2025
@marissahuysentruyt marissahuysentruyt added size-5 L ~30-42hrs; lots of effort or complexity, most of a sprint needed to complete. wip This is a work in progress, don't judge. skip_vrt Add to a PR to skip running VRT (but still pass the action) S2 Spectrum 2 labels May 20, 2025
Copy link
Contributor

github-actions bot commented May 20, 2025

🚀 Deployed on https://pr-3799--spectrum-css.netlify.app

Copy link
Contributor

github-actions bot commented May 20, 2025

File metrics

Summary

Total size: 1.42 MB*

Package Size Minified Gzipped
table 43.80 KB 41.77 KB 5.08 KB

table

Filename Head Minified Gzipped Compared to base
index.css 43.80 KB 41.77 KB 5.08 KB 🟢 ⬇ 3.28 KB
metadata.json 20.55 KB - - 🟢 ⬇ 1.37 KB
* Size is the sum of all main files for packages in the library.
* An ASCII character in UTF-8 is 8 bits or 1 byte.

@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/css-1178-s2-table-migration-layout-pt1 branch from 25578c0 to 8a6fb2f Compare May 21, 2025 19:35
Copy link

changeset-bot bot commented May 21, 2025

🦋 Changeset detected

Latest commit: 101b69b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@spectrum-css/table Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/css-1178-s2-table-migration-layout-pt1 branch 2 times, most recently from 36920dd to 490a4e9 Compare May 21, 2025 22:18
Visuals.storyName = "With visuals";


// TODO: The design team doesn't have support for collapsible rows in the table component, so they are removed from the docs page for now.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a couple TODO notes throughout the stories file here, reflecting on the conversation I had with Stefan.

Design doesn't have collapsible row designs, or dropzone designs done (and they're unsure if they will). React only has beta drag & drop table components.

So I didn't really want to get rid of our stories or tests for those table variants/options. What are our thoughts on just removing these 5 or 6 stories regarding collapsible rows and dropzones from the autodocs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense to me 👍

Sizing.args = {};
Sizing.tags = ["!dev"];
Sizing.parameters = {
export const EmptyState = Template.bind({});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love some feedback on the empty and loading states. Should I do anything more than return the illustrated message or progress circle component? As in, do I return the header row, AND THEN render the illustrated message/progress circle, with those 2 components like "in" the table? That's not really what Figma looks like, but...open to feedback on these two new stories.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not render any of the table semantics. We could talk to design about having a "skeleton" loading state to populate a few rows which would be wrapped with aria-busy="true" until the data loaded, as described by Sara. That would also have a benefit of holding space and possibly reducing a page jump when content came in. It might be appropriate to couple with a live region as well, which would be more app-dependent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I love the idea of a skeleton loader. However, that also sounds like a separate ticket and PR. I'll make a ticket for it, along with some of the comments above, just so we have this idea documented! Thank you!

>
${when(showCheckbox && !isSectionHeader, () => html`
<${cellTag}
role="gridcell"
class=${classMap({
[`${rootClass}-cell`]: true,
[`${rootClass}-checkboxCell`]: true,
[`${rootClass}-cell--alignEnd`]: getTextAlignment(0) === "end",
...cellCustomClasses?.[0]?.reduce((a, c) => ({ ...a, [c]: true }), {}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to show the individual cell focus states, so I had to pass custom classes to the cells.

})}
tabindex="0"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rows should be keyboard focusable, as well as the cells (according to the designs), so I added the tabindex in quite a few places.

Any accessibility concerns with how everything is laid out right now? I don't know the flow for tables. Should each focused row move to the next focusable row, or would a user focus on a row, then move through the cells?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tabindex should not be included by default, as screen readers have a specific mode to navigate tables. And for keyboard nav, it may be ok to use the roving tabindex technique like React does, but that is JS-controlled based on key presses.

A tab stop tends to indicate some kind of interaction, so if the row isn't selectable, it shouldn't be a tab stop. And if it is selectable, then again I think roving tabindex is more appropriate than forcing every row to be a tab stop via tabindex. But - implementing roving tabindex would be on SWC, I think.

It looks like SWC currently doesn't support sortable columns, but if we are demonstrating that, then it still wouldn't be the th receiving focus, but a nested button like this accessible sortable table example

An exception is the scroller utility, which should have tabindex="0" to allow keyboard access to scroll that region, which is also demonstrated in the sortable table article.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all of this info and the example! I removed the tab index instances I added ✅ (except for one- the third column header cell was the only header cell that didn't have the tabindex).

As for the scroller utility- we demo the scrollable , and I can scroll with the arrow keys, no problem, both with table elements and made entirely of divs.

I may add some documentation to the "Row states" story (that has a bunch of different focus and selected combinations) to mention something about "implementations should add appropriate tab stops to indicate focus is on a row."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think that tabindex will go away when you update this to be a button instead (per Slack discussion), since that will provide the tab stop.

Scrolling via keyboard works as I expect 👍

*/
export const Sizing = (args, context) => Sizes({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like we ever really supported sizes actually. The guidance site doesn't refer to table sizes, nor do the current Figma designs for S2 🤷‍♀️

@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/css-1178-s2-table-migration-layout-pt1 branch from 14fcf31 to 4b76b0c Compare May 22, 2025 18:43
@@ -204,65 +285,75 @@ export const Template = ({
id=${ifDefined(id)}
role=${ifDefined(useCheckboxCell ? "grid" : useDivs ? "table" : undefined)}
aria-multiselectable=${ifDefined(useCheckboxCell ? "true" : undefined)}
style="max-width: 800px;"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I keep this? Is it necessary? I'm fine with adding this max-width back on, just let me know!

isChecked: false,
isIndeterminate: true,
customClasses: [`${rootClass}-checkbox`],
${when(selectionMode === "multiple", () => html`
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkboxes should only appear in the thead if users can select more than one row (and this puts the indeterminate checkbox in that header row). Otherwise, checkboxes for single select rows just appear in their rows.

tabindex="0"
>
${Icon({
iconName: "Sort",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added each of the new sort icons to each header cell.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should remove these as far as the default display, and only have them within a Story related to sorting? Then we can also include any relevant docs for using sorting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can do that!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New story has good info! Will re-look when updated to the button.

customClasses: [`${rootClass}-sortIcon`],
}, context)}
<span class="${rootClass}-columnTitle">Column title</span>
${Icon({
Copy link
Collaborator Author

@marissahuysentruyt marissahuysentruyt May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second column now has the additional "menu" or disclosure icon. It's non functional but...should this be a button? Like an action button to trigger a menu?

The rest of the collapsible disclosure icons are all buttons so...that's what triggered the question for me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible both this and the sortable icon (noted in the "tabindex" comment) should be a button.

Maybe first get ideas from design on what is going to show up in the menu? For example, if it's just going to show the sort options, then that would result in one button that launches the menu vs. separate actions.

The scenario of two, separate actions is a little fuzzy to me as far as handling the labeling and sorted status accessibly... maybe if design validates that could happen, you could describe the scenario and ask in the a11y Slack channel?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked Stefan about the possibilities for the menu items. 👍 I'll also send you a React link in slack for some of their menu options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responded in Slack 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this thread related to the disclosure icon that's in the designs? I've not been able to find a story where this appears.
image

Comment on lines 410 to 492
&::after {
content: "";
position: absolute;
inset: calc(-1 * var(--mod-table-border-width, var(--spectrum-table-border-width)));
border-radius: calc(var(--mod-table-border-radius, var(--spectrum-table-border-radius)) - var(--mod-table-border-width, var(--spectrum-table-border-width)));
border: var(--mod-table-focus-indicator-thickness, var(--spectrum-table-focus-indicator-thickness)) solid var(--highcontrast-table-cell-focus-indicator-color, var(--highcontrast-table-focus-indicator-color, var(--mod-table-focus-indicator-color, var(--spectrum-table-focus-indicator-color))));
pointer-events: none;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cells have a different focus indicator border radius, than they do when non-focused. I'd love some feedback on this ::after approach to achieve that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

--highcontrast-table-icon-color: var(--highcontrast-table-row-text-color-hover);
--spectrum-table-cell-background-color: var(--highcontrast-table-row-background-color-hover, var(--mod-table-row-active-color, var(--spectrum-table-row-active-color)));
}
/* First cell of focused rows need fancy new focus indicator borders & no border-radius, and the thicker row focus indicator */
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was pretty complex, so again- feedback is welcome!

The table row itself can be focused.
It's got a 4px blue side focus indicator, as well as (basically) a border that goes around the entire row.
Some rows don't have border radii, and some do. But- those border radii are created with the individual table cells in that row.

🥵🥵🥵

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed there is some content shift happening when focusing on a row (which I didn't notice before). Anybody have ideas on how to avoid that? I think it's because I am adding the border-block-end to some cells of the row, which ultimately adds an extra 1px.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sent a message to Stefan to clarify the "borders around a focused row." That might just be for an emphasized table? I need some clarity on that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you added a different TODO comment re: the shift?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! the layout shift was addressed in this commit: c7a3a1e

padding-block-start: calc(var(--mod-table-row-top-to-text, var(--spectrum-table-row-top-to-text)) - var(--mod-table-border-width, var(--spectrum-table-border-width)));
padding-block-end: var(--mod-table-row-bottom-to-text, var(--spectrum-table-row-bottom-to-text));
}
align-items: center;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling all feedback again!

The guidance says: "All content should be centered vertically within the row to offer the right visual balance." That's why I added the align-items: center, ripped out all of the custom padding, and centered everything everywhere else. That does however, prove difficult in collapsible rows for a number of reasons:

  • the button component (with the disclosure icon) is just tall, so the padding around the cells beefs the height up.
  • it's really weird to have a cell with wrapped text where everything is aligned to the center
Screenshot 2025-05-22 at 3 06 35 PM

However....collapsible rows aren't quite designed out so we don't have any true guidance on how to handle any of that spacing. I removed them from the docs page (pending others' agreement/approval). Should I reimplement some of this padding and alignment handling since the collapsible table variants are still in the test grid? How would we like to handle this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While text that's truly that long is probably an outlier, I could see an argument for start-aligning the icon with the text specifically as an accessibility benefit for users of zoom or screen magnification who see less of the screen at one time. So, having the collapse icon so separated from the labeling text might create a barrier in noticing the visual cue of the icon. (same issue possible for selectable rows)

So - maybe we can bring up this point with them and hold on further changes as far as this PR is concerned?

Copy link
Collaborator Author

@marissahuysentruyt marissahuysentruyt May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll hold on changing anymore for the collapsible variants.

For further deets on this from my conversation with Stefan- he mentioned that really, if design were to support collapsible rows (they don't currently), he would use "accordion items." I'm not sure if he means that engineering should also use an accordion item but that feels semi-related to our conversation about column headers being buttons too.

I'm happy to create another card to revisit this!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of the collapsible variant, if "all content should be centered vertically within the row," why do we have top and bottom spacing tokens for rows? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Stefan, the guidance is out of date. He also mentioned that the "uneven" padding is temporary until the new font is incorporated.

@marissahuysentruyt marissahuysentruyt added ready-for-review and removed wip This is a work in progress, don't judge. labels May 22, 2025
-instead, continue the use of the individual cell border-block-start in
the quiet variant, but use the divider color instead of border-color
- updates a missing token for focused cells
- removes unused token for row focus indicator outline width
- adds story for a table with a menu action button in the header cell
- adds hasMenu story arg and new markup
- sets the hasMenu arg to false
- adds a better description for the emphasized arg
- adds argTypes to the EmptyState story
- fixes the action button link
- changes "left-aligned" to "start-aligned"
- removes section header quiet from side nav
- removes action button markup in favor of regular button
- adjusts template for sort-only and menu buttons
- updates some class names
- sort-only renders a button in the head cell, and menu renders a button
in the head cell with the sort icon and disclosure icon
- the sort-only and menu button styles are now the same since the markup
changed
- new passthrough mods for the button component were refactored from the
action button component
- removes unused sortIcon and menuIcon selectors/styles
- remove trailingIcon arg
- correctly adds aria-multiselectable to the table when the
selectionMode is set to "multiple"
- updates to docs page
- updates to documentation to clear up additional classes for the
sort button and menu button
- updates the template to use the new classes
- updates the aria sort attribute to be set on the head cell if hasMenu
is true
- updates the new classes in the CSS
Copy link
Contributor

@5t3ph 5t3ph left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whooohoo, excellent work!! 🙌

@marissahuysentruyt marissahuysentruyt force-pushed the marissahuysentruyt/css-1178-s2-table-migration-layout-pt1 branch from 5f0b680 to 101b69b Compare June 5, 2025 21:13
@marissahuysentruyt marissahuysentruyt merged commit c86d476 into spectrum-two Jun 5, 2025
12 checks passed
@marissahuysentruyt marissahuysentruyt deleted the marissahuysentruyt/css-1178-s2-table-migration-layout-pt1 branch June 5, 2025 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-for-review S2 Spectrum 2 size-5 L ~30-42hrs; lots of effort or complexity, most of a sprint needed to complete. skip_vrt Add to a PR to skip running VRT (but still pass the action)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants