Skip to content

Conversation

@nellicus
Copy link
Contributor

@nellicus nellicus commented Oct 13, 2025

Add configurable column visibility to Table component

This PR adds the ability for users to show/hide optional columns in the Table component, with preferences automatically persisted to storage.

New Features

  • Column visibility toggle: Settings icon in the table header opens a popover to configure visible columns
  • Mandatory columns: Mark columns as mandatory: true to prevent them from being hidden
  • Persistent preferences: Column visibility choices are automatically saved and restored

New Props

TableHeaderType:

  • id?: string - Unique identifier for the column (auto-generated if not provided)
  • required?: boolean - If true, column cannot be hidden by the user
  • selected?: boolean - alternative to required: if true, the non-required column will be rendered as optional and selected

Table:

  • enableColumnVisibility?: boolean - Enables the column visibility feature (default: false)
  • tableId?: string - Unique identifier for storing preferences (required when enableColumnVisibility is true)
  • onLoadColumnVisibility?: (tableId: string) => Record<string, boolean> - Optional custom function to load preferences (defaults to localStorage)
  • onSaveColumnVisibility?: (tableId: string, visibility: Record<string, boolean>) => void - Optional custom function to save preferences (defaults to localStorage)

Usage

<Table
  headers={[
    { id: "name", label: "Name", mandatory: true }, // Always visible
    { id: "email", label: "Email" },                // User can hide
    { id: "role", label: "Role" }                   // User can hide
  ]}
  rows={rows}
  enableColumnVisibility={true}
  tableId="users-table"
/>

By default, preferences are saved to localStorage with key click-ui-table-column-visibility-{tableId}. Optionally provide custom storage functions for alternative storage
mechanisms (API, IndexedDB, sessionStorage, etc.).

Backward Compatibility

✅ Fully backward compatible - existing tables continue to work unchanged. The feature is opt-in via enableColumnVisibility prop.

Screenshot 2025-10-13 at 16 14 56 Screenshot 2025-10-13 at 16 15 02 Screenshot 2025-10-13 at 16 15 07
click-ui-table-column-visibility.webm

@vercel
Copy link

vercel bot commented Oct 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
click-ui Ready Ready Preview Comment Oct 15, 2025 3:58pm

…long as the string contains a quote that would have to be escaped otherwise
Copy link
Collaborator

@ariser ariser left a comment

Choose a reason for hiding this comment

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

awesome job!
a few things I noticed:

sortDir?: SortDir;
sortPosition?: HorizontalDirection;
width?: string;
mandatory?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

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

NP:
requried? a more common term, I think
or maybe configurable: false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

/>
))}
{actionsList.length > 0 && <col width={(actionsList.length + 1) * 32 + 10} />}
{enableColumnVisibility && <col width={48} />}
Copy link
Collaborator

Choose a reason for hiding this comment

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

this includes padding, making the actual width less than the button width

Image

Comment on lines +192 to +203
{enableColumnVisibility && (
<StyledHeader
aria-label="Column visibility"
$size={size}
>
<ColumnVisibilityPopover
headers={headers}
visibleColumns={visibleColumns}
onVisibilityChange={onVisibilityChange}
/>
</StyledHeader>
)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should merge this heading cell with the actions heading cell
here, the columns icon should be above the actions column:
Image

yes, we don't use these actions that much, and we should change how they are implemented
but still

</ActionsContainer>
</ActionsList>
)}
{enableColumnVisibility && <TableData $size={size} />}
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is this?

<EllipsisContent component="div">{label}</EllipsisContent>
</TableData>
))}
{visibleItems.map(({ label, ...cellProps }, visibleIndex) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

looks like on mobile we don't have a way to configure visible columns
we need to keep the button available somewhere


headers.forEach((header, index) => {
const columnId = header.id || `column-${index}`;
// If mandatory, always visible. Otherwise, check stored preference (default true)
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we have a way to mark columns as initially hidden?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

>
<Checkbox
checked={isVisible}
disabled={isMandatory}
Copy link
Collaborator

Choose a reason for hiding this comment

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

i can suggest using the lock icon instead of disabled checkbox for locked columns
smth like
Image

maybe not make it muted as well, not that it's something secondary, it's just not available for configuration

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

Comment on lines 986 to 993
<ColumnVisibilityHeader>
<Text
size="sm"
weight="semibold"
>
Columns
</Text>
</ColumnVisibilityHeader>
Copy link
Collaborator

Choose a reason for hiding this comment

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

don't think this heading serves any purpose, lets not add it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

display: flex;
align-items: center;
gap: 0.5rem;
cursor: ${({ $disabled }) => ($disabled ? "not-allowed" : "pointer")};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Actual interactive area doesn't match the visually interactive area.
You can click at the empty space in the row, and it'll work. But usualy a checkbox clickable area is limited to its label, where there's an actual text.

It's a good thing that the row is clickable, but let's add visual feedback to make it obvious: a hover effect for the whole row, sometimng similar to what we have in CheckboxMultiSelect, or Dropdown

size?: TableSize;
showHeader?: boolean;
rowHeight?: string;
tableId?: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

when not specified, it will silently not do anything
we need to make it required when the primary prop is enabled

type Type = 
  | {
    enabled: true;
    tableId: string;
  }
  | {
    enabled?: false;
    tableId?: never;
  };

<Popover.Trigger>
<IconButton
type="ghost"
icon="settings"
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think I'm more used to seeing a gear icon in this context
but idk, this one works as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

…s the non required column), rename mandatory -> required, use lock icon for required col
@nellicus nellicus changed the title [table] column visibility [table] configurable columns Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants