From 3e14dcbb05ac3721f0f1b9190a49581fc8dd46bf Mon Sep 17 00:00:00 2001
From: Gautam pandit <113331777+Gautam5514@users.noreply.github.com>
Date: Mon, 20 Jul 2026 21:30:31 +0530
Subject: [PATCH] Fix record board column header action accessibility (#22496)
## Summary
- Keep Record Board column header actions mounted instead of rendering
them only on mouse hover
- Show actions on hover and focus-within so keyboard users can reach
them
- Avoid header layout shifts when actions appear
## Context
This is a small follow-up found while reviewing #22323. It does not
duplicate the Kanban column drag-and-drop implementation.
## Testing
- git diff --check
- Not run: package lint/typecheck because this checkout still has no
node_modules and Yarn is not available on PATH
---------
Co-authored-by: bosiraphael
---
.../components/RecordBoardColumnHeader.tsx | 85 ++++++++++++-------
1 file changed, 53 insertions(+), 32 deletions(-)
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
index 1d0a575db6..f7d71abd2d 100644
--- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx
@@ -1,5 +1,6 @@
import { styled } from '@linaria/react';
-import { useContext, useState } from 'react';
+import { t } from '@lingui/core/macro';
+import { useContext } from 'react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
@@ -21,6 +22,7 @@ import { canCreateRecordsForObjectMetadataItem } from '@/object-record/utils/can
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useDisableDragSelectOnPointerDown } from '@/ui/utilities/drag-select/hooks/useDisableDragSelectOnPointerDown';
import { useToggleDropdown } from '@/ui/layout/dropdown/hooks/useToggleDropdown';
+import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@@ -38,8 +40,31 @@ const StyledHeader = styled.div`
`;
const StyledHeaderActions = styled.div`
+ align-items: center;
display: flex;
- margin-left: auto;
+ flex-shrink: 0;
+ // padding + negative margin cancel out in layout and exist only so
+ // overflow:hidden clips 4px outside each button, leaving room for
+ // LightIconButton's 3px focus ring
+ margin: calc(-1 * ${themeCssVariables.spacing[1]});
+ max-width: 0;
+ min-width: 0;
+ opacity: 0;
+ overflow: hidden;
+ padding: ${themeCssVariables.spacing[1]};
+ pointer-events: none;
+ transition:
+ max-width ease-in-out
+ calc(${themeCssVariables.animation.duration.fast} * 1s),
+ opacity ease-in-out calc(${themeCssVariables.animation.duration.fast} * 1s);
+
+ &[data-dropdown-open='true'],
+ ${StyledHeader}:hover &,
+ ${StyledHeader}:focus-within & {
+ max-width: ${themeCssVariables.spacing[14]};
+ opacity: 1;
+ pointer-events: auto;
+ }
`;
const StyledHeaderContainer = styled.div`
@@ -55,11 +80,6 @@ const StyledLeftContainer = styled.div`
overflow: hidden;
`;
-const StyledRightContainer = styled.div`
- align-items: center;
- display: flex;
-`;
-
const StyledColumn = styled.div`
background-color: ${themeCssVariables.background.primary};
display: flex;
@@ -104,8 +124,6 @@ export const RecordBoardColumnHeader = () => {
onPointerUp: handlePointerUp,
} = useDisableDragSelectOnPointerDown();
- const [isHeaderHovered, setIsHeaderHovered] = useState(false);
-
const { objectMetadataItem, selectFieldMetadataItem } =
useContext(RecordBoardContext);
@@ -140,6 +158,11 @@ export const RecordBoardColumnHeader = () => {
const dropdownId = `record-board-column-dropdown-${columnDefinition.id}`;
+ const isDropdownOpen = useAtomComponentStateValue(
+ isDropdownOpenComponentState,
+ dropdownId,
+ );
+
const handleCreateNewRecordClick = async () => {
await createNewIndexRecord({
position: 'first',
@@ -152,8 +175,6 @@ export const RecordBoardColumnHeader = () => {
0 ? 'true' : undefined}>
setIsHeaderHovered(true)}
- onMouseLeave={() => setIsHeaderHovered(false)}
onPointerCancel={handlePointerCancel}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
@@ -187,28 +208,28 @@ export const RecordBoardColumnHeader = () => {
aggregateLabel={recordIndexAggregateDisplayLabel}
/>
-
- {isHeaderHovered && (
-
- {
- toggleDropdown({
- dropdownComponentInstanceIdFromProps: dropdownId,
- });
- }}
- />
- {canCreateRecords && !hasAnySoftDeleteFilterOnView && (
-
- )}
-
+
+ {
+ toggleDropdown({
+ dropdownComponentInstanceIdFromProps: dropdownId,
+ });
+ }}
+ />
+ {canCreateRecords && !hasAnySoftDeleteFilterOnView && (
+
)}
-
+