From b2d8865ca77d1d8a5347c34aeea55d83ee7c37d5 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Thu, 21 May 2026 18:08:47 -0700 Subject: [PATCH 1/3] chore: remove box-shadow divider and update focus ring in TableView --- packages/@react-spectrum/s2/src/TableView.tsx | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 57922060510..65733d9fac9 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -2016,11 +2016,14 @@ const row = style< } } }, - // When checkbox selection, render the gray divider between rows as a border borderTopWidth: 0, borderBottomWidth: { selectionStyle: { - highlight: 0, + highlight: { + default: 1, + isSelected: 0, + isNextSelected: 0 + }, checkbox: 1 } }, @@ -2029,7 +2032,10 @@ const row = style< borderStyle: 'solid', borderColor: { selectionStyle: { - highlight: 'transparent', + highlight: { + default: 'gray-300', + isSelected: 'transparent' + }, checkbox: 'gray-300' } }, @@ -2057,20 +2063,11 @@ const row = style< isSelected: '--borderColorBlue' } }, - // When highlight selection, render gray dividers as box shadow - boxShadow: { - selectionStyle: { - highlight: { - default: '[inset 0 -1px 0px var(--borderColorGray)]', - isNextSelected: '[inset 0 0 0 var(--borderColorGray)]' - // TODO: Determine if we want to support gray dividers between selected grouped rows - // isSelected: { - // isNextSelected: '[inset 0 -1px 0px var(--borderColorGray)]' - // } - } - }, - forcedColors: { - isFocusVisible: '[inset 0 0 0 2px Highlight]' + '--focusRingColor': { + type: 'outlineColor', + value: { + default: 'focus-ring', + forcedColors: 'Highlight' } }, fontWeight: { @@ -2078,11 +2075,30 @@ const row = style< isInFooter: 'bold' }, isolation: 'isolate', - forcedColorAdjust: 'none' + forcedColorAdjust: 'none', + '--topPosition': { + type: 'top', + value: { + default: '[-1px]', + isFirstItem: 0 + } + }, + '--bottomPosition': { + type: 'bottom', + value: { + default: '[-1px]', + selectionStyle: { + highlight: { + default: '[-1px]', + isSelected: 0 + } + } + } + } }); // Sticky cells (the drag cell, and the checkbox cell when present) get an inline z-index=2 applied by the virtualizer's layout -// To ensure that the highlight selection border is painted above the stick cells, set z-index to 3 +// To ensure that the highlight selection border is painted above the sticky cells, set z-index to 3 const highlightSelectionBorder = css( `&:before { content: ""; @@ -2110,15 +2126,15 @@ const highlightSelectionBorder = css( const focusIndicator = css( `&:after { content: ""; - width: 100%; - height: 100%; - top: 0; - z-index: 2; + top: var(--topPosition); + bottom: var(--bottomPosition); + z-index: 3; inset-inline-start: 0; + inset-inline-end: 0; border-radius: 5px; position: absolute; outline-style: solid; - outline-color: var(--borderColorBlue); + outline-color: var(--focusRingColor); outline-width: 2px; outline-offset: -2px; pointer-events: none; @@ -2174,6 +2190,7 @@ export const Row = /*#__PURE__*/ (forwardRef as forwardRefType)(function Row( ...tableVisualOptions, selectionStyle, isInFooter, + isFirstItem: isFirstItem(renderProps.id, renderProps.state), isNextSelected: isNextSelected(renderProps.id, renderProps.state), isPrevSelected: isPrevSelected(renderProps.id, renderProps.state) }) + @@ -2254,3 +2271,10 @@ export function isPrevSelected(id: Key | undefined, state: TableState) let keyBefore = state.collection.getKeyBefore(id); return keyBefore != null && state.selectionManager.isSelected(keyBefore); } + +function isFirstItem(id: Key | undefined, state: TableState) { + if (id == null || !state) { + return false; + } + return state.collection.getFirstKey() === id; +} From 01ba1a6bd44c6a0ed828688a195fc02c5b93b92d Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Fri, 22 May 2026 10:41:30 -0700 Subject: [PATCH 2/3] fix lint --- packages/@react-spectrum/s2/src/TableView.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 65733d9fac9..9cd08e1d01b 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -1913,7 +1913,12 @@ const rowTextColor = { const row = style< RowRenderProps & - S2TableProps & {isInFooter?: boolean; isNextSelected?: boolean; isPrevSelected?: boolean} + S2TableProps & { + isInFooter?: boolean; + isNextSelected?: boolean; + isPrevSelected?: boolean; + isFirstItem?: boolean; + } >({ height: 'full', position: 'relative', From 3e6bb521e8ee27738c5328d59b5aedee234ad8bf Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Fri, 22 May 2026 14:16:08 -0700 Subject: [PATCH 3/3] simplify borders, improve focus ring --- packages/@react-spectrum/s2/src/TableView.tsx | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index 9cd08e1d01b..dc8b46ba1f1 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -688,7 +688,15 @@ function CellFocusRing() { return (
@@ -2022,16 +2030,7 @@ const row = style< } }, borderTopWidth: 0, - borderBottomWidth: { - selectionStyle: { - highlight: { - default: 1, - isSelected: 0, - isNextSelected: 0 - }, - checkbox: 1 - } - }, + borderBottomWidth: 1, borderStartWidth: 0, borderEndWidth: 0, borderStyle: 'solid', @@ -2039,7 +2038,8 @@ const row = style< selectionStyle: { highlight: { default: 'gray-300', - isSelected: 'transparent' + isSelected: 'transparent', + isNextSelected: 'transparent' }, checkbox: 'gray-300' } @@ -2081,22 +2081,34 @@ const row = style< }, isolation: 'isolate', forcedColorAdjust: 'none', - '--topPosition': { + '--topFocusRing': { type: 'top', value: { default: '[-1px]', isFirstItem: 0 } }, + '--topHighlightBorder': { + type: 'top', + value: { + default: 0, + isSelected: '[-1px]', + // Don't overlap focus ring of row above. + isPrevSelected: 0, + isFirstItem: 0, + forcedColors: 0 + } + }, '--bottomPosition': { type: 'bottom', value: { - default: '[-1px]', selectionStyle: { - highlight: { + checkbox: { default: '[-1px]', - isSelected: 0 - } + // Avoid the next row's selected background covering this row's focus ring. + isNextSelected: 0 + }, + highlight: '[-1px]' } } } @@ -2107,12 +2119,11 @@ const row = style< const highlightSelectionBorder = css( `&:before { content: ""; - width: 100%; - height: 100%; + top: var(--topHighlightBorder); + bottom: -1px; + inset-inline: 0; position: absolute; - inset: 0; z-index: 3; - box-sizing: border-box; border-style: solid; border-color: var(--borderColor); border-top-width: var(--borderTopWidth); @@ -2131,7 +2142,7 @@ const highlightSelectionBorder = css( const focusIndicator = css( `&:after { content: ""; - top: var(--topPosition); + top: var(--topFocusRing); bottom: var(--bottomPosition); z-index: 3; inset-inline-start: 0;