Tech TutorBangla Web Documentation
Portfolio
HomeHTMLCSSTailwind CSSJavaScriptTypeScriptReactNext.jsNode.jsExpressSocket.IOExpoWordPressPostgreSQLPrismaPostgresNestJSMongoDBDockerTanStackTanStack TableRTK QueryGitGitHub
  • TanStack Table Documentation12টি অধ্যায়
প্রযুক্তি নির্বাচন
HomeHTMLCSSTailwind CSSJavaScriptTypeScriptReactNext.jsNode.jsExpressSocket.IOExpoWordPressPostgreSQLPrismaPostgresNestJSMongoDBDockerTanStackTanStack TableRTK QueryGitGitHub
  • Introduction
  • Installation & Setup
  • Core Concepts
  • Column Definitions
  • Row Models
  • Sorting
  • Filtering
  • Pagination
  • Row Selection
  • Column Management
  • Server-side Mode
  • Advanced Features
  1. Docs
  2. TanStack Table
  3. Column Management — visibility, order, pinning ও sizing
TanStack TableIntermediate

Column Management — visibility, order, pinning ও sizing

columnVisibility, columnOrder, columnPinning (sticky কলাম), columnSizing ও resize mode, localStorage-এ সংরক্ষণ — বাংলায়।

46টি অংশবাংলা ভাষায়

একটা ভালো data table-এ কলাম ব্যবহারকারী নিজের মতো সাজাতে পারে — লুকাতে পারে, আগে-পিছে বদলাতে পারে, প্রস্থ টানতে পারে। এই চারটি state TanStack-এ আলাদা আলাদা, কিন্তু কাজ একসাথে করে।

চারটি state, এক look-এ

import { useState } from "react";
import type {
  ColumnOrderState,
  ColumnPinningState,
  ColumnSizingState,
  VisibilityState,
} from "@tanstack/react-table";

const [columnVisibility, setColumnVisibility] =
  useState<VisibilityState>({});
const [columnOrder, setColumnOrder] =
  useState<ColumnOrderState>([]);
const [columnPinning, setColumnPinning] =
  useState<ColumnPinningState>({ left: [], right: [] });
const [columnSizing, setColumnSizing] =
  useState<ColumnSizingState>({});
stateআকারকী করে
columnVisibility{ price: false }কলাম লুকায়/আনে
columnOrder["name", "price", …]কলামের ক্রম বদলায়
columnPinning{ left: ["select"], right: ["actions"] }কলাম পাশে আটকায়
columnSizing{ price: 180 }কলামের প্রস্থ

Column visibility

// নির্দিষ্ট কলাম লুকানো
table.getColumn("createdAt")?.toggleVisibility(false);
table.getColumn("createdAt")?.setVisibility(true);
table.getColumn("createdAt")?.getIsVisible();   // boolean

// সব ফিরিয়ে আনা
table.resetColumnVisibility();
table.getVisibleLeafColumns().length;
table.getAllLeafColumns().length;
table.getCenterVisibleLeafColumns().length;

লুকানো কলাম row.getVisibleCells()-এ আসেই না — তাই rendering কোডে কোনো শর্ত লেখতে হয় না। এটাই headless আর্কিটেকচারের সবচেয়ে বড় সুবিধা: feature যোগ হলেও markup অপরিবর্তিত থাকে।

Column visibility মেনু

import { Checkbox } from "@/components/ui/checkbox";
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function ColumnVisibility({
  table,
}: {
  table: Table<Product>;
}) {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">
          <Settings2 className="mr-2 size-4" />
          কলাম
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="w-48">
        {table
          .getAllLeafColumns()
          .filter((column) => column.getCanHide())
          .map((column) => (
            <DropdownMenuCheckboxItem
              key={column.id}
              checked={column.getIsVisible()}
              onCheckedChange={(checked) =>
                column.toggleVisibility(checked === true)
              }
            >
              {columnLabel(column.id)}
            </DropdownMenuCheckboxItem>
          ))}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

getCanHide() false হলে (checkbox কলাম, actions কলাম) তালিকায় আসে না — ঐপ্রিন্স ছাড়া লুকিয়ে দেওয়ার কোনো কারণ নেই।

Column order — drag & drop

// ম্যানুয়ালি সাজানো
table.setColumnOrder(["select", "name", "price", "actions"]);
table.getState().columnOrder;

// একটি কলাম সরানো
const order = table.getState().columnOrder;
const next = order.filter((id) => id !== "price");
next.splice(2, 0, "price");
table.setColumnOrder(next);

// ডিফল্ট ক্রমে ফেরা
table.resetColumnOrder();

columnOrder খালি ([]) রাখলে columns-এর লেখা ক্রমই ব্যবহার হয়। তাই সাধারণত শুধু user কিছু সরালে অ্যারে বসান, না সরালে ফাঁকা রাখুন — তবে "রিসেট" বাটনে resetColumnOrder() দিন।

Column pinning — sticky কলাম

বড় টেবিলে পাশে স্ক্রল করলে actions কলামটা চলে যায় — ব্যবহারকারীর জন্য অস্বস্তি। Pin করলে সেটা বসে থাকে।

// বাঁয়ে / ডানে আটকানো
table.getColumn("actions")?.pin("right");
table.getColumn("name")?.pin("left");

table.getColumn("actions")?.pin();     // unpin (কোনো দিকেও নয়)
table.getColumn("actions")?.getIsPinned();          // false | "left" | "right"
table.getColumn("actions")?.getIsLastColumn();
table.getColumn("actions")?.getIsFirstColumn();

// state দেখা
table.getState().columnPinning;
// { left: ["select", "name"], right: ["actions"] }

TanStack flexible header group বানায় — pinned কলাম আলাদা <th> পায়, তাই position: sticky বা shadcn-এর ঐ class দিতে হয়।

Sticky কলাম CSS

// রেন্ডারে পাশের কলামে sticky class
<TableHead
  className={cn(
    "bg-background",
    header.column.getIsLastColumn() && "sticky right-0 z-20",
    header.column.getIsPinned() && "shadow-[inset_-1px_0_0_0_currentColor]",
  )}
>
// body cell-এও একই কাজ
<TableCell
  className={cn(
    "bg-background",
    cell.column.getIsLastColumn() && "sticky right-0 z-10",
  )}
>
  …
</TableCell>
sticky কলামের ব্যাকগ্রাউন্ড

Sticky কলামে background অবশ্যই দিতে হবে, আর খারাপ হলে bg-muted। না দিলে কনটেন্ট উপর দিয়ে ভেসে যায় আর পড়া যায় না। z-index দুই ধাপ — header-এর z-20, cell-এর z-10, যাতে header সবসময় উপরে থাকে।

Pin লেখা / unpin লেখা

<DropdownMenuSub>
  <DropdownMenuSubTrigger>
    {cell.column.getIsPinned() ? "আটকানো আছে" : "আটকানো নেই"}
  </DropdownMenuSubTrigger>
  <DropdownMenuPortal>
    <DropdownMenuSubContent>
      <DropdownMenuItem onClick={() => cell.column.pin("left")}>
        <PinIcon className="mr-2 size-4" />
        বাঁয়ে আটকান
      </DropdownMenuItem>
      <DropdownMenuItem onClick={() => cell.column.pin("right")}>
        <PinIcon className="mr-2 size-4 rotate-180" />
        ডানে আটকান
      </DropdownMenuItem>
      {cell.column.getIsPinned() && (
        <DropdownMenuItem onClick={() => cell.column.pin(false)}>
          <PinOffIcon className="mr-2 size-4" />
          আটকানো সরান
        </DropdownMenuItem>
      )}
    </DropdownMenuSubContent>
  </DropdownMenuPortal>
</DropdownMenuSub>

Column sizing — প্রস্থ

// ডিফল্ট প্রস্থ (row model অনুযায়ী)
columnSizing: {
  minSize: 20,     // সর্বনিম্ন ২০px
  maxSize: 1000,   // সর্বোচ্চ ১০০০px
}

// প্রতি কলামে ডিফল্ট
{ accessorKey: "name", size: 280, minSize: 150, maxSize: 600 }

// resize mode
columnResizeMode: "onChange", // টানা গ্লাইস মুভ হচ্ছে (default)
columnResizeMode: "onEnd",   // ছাড়লে হিসাব হয় — বেশি smooth

// state
column.getSize()          // বর্তমান px
column.setSize(220)
column.getIsResizing()
table.setColumnSizing({ price: 220 })
table.resetColumnSizing();

onEnd মোডে হিসাব শুধু ছেড়ে দিলে হয়, তাই বড় টেবিলে এটা বেশি performant। onChange মোডে প্রতিটি মাউস মুভে layout recalc হয়।

Resize handle

// শুধু ঐ কলামের পাশে একটা পাতলা handle
export function ResizeHandle({
  header,
}: {
  header: Header<unknown, unknown>;
}) {
  const { column } = header;
  const isResizing = column.getIsResizing();

  return (
    <div
      onMouseDown={header.getResizeHandler()}
      onTouchStart={header.getResizeHandler()}
      role="separator"
      aria-orientation="vertical"
      aria-label={`${column.id} প্রস্থ বদলান`}
      className={cn(
        "absolute top-0 right-0 h-full w-1 cursor-col-resize touch-none select-none",
        "after:absolute after:right-0 after:h-full after:w-1",
        isResizing ? "after:bg-primary" : "after:bg-transparent",
        "hover:after:bg-primary/50",
      )}
      style={{
        left: isPinning(column.id) ? `${column.getSize()}px` : undefined,
        right: isPinnedRight(column.id) ? `-${column.getSize()}px` : undefined,
      }}
    />
  );
}
// TableHead-এর মধ্যে
export function TableHead({ children, column }: HeaderCellProps) {
  return (
    <div className="relative">
      {children}
      {column.getCanResize() && <ResizeHandle column={column} />}
    </div>
  );
}

shadcn-এর TableHead শুধু th রেন্ডার করে, ভেতরে div বসানো যায় না। handle বসাতে চাইলে columnResizeMode ব্যবহার না করে নিজে th-এর onMouseDown ধরে column.setSize কল করাও সহজ — header.getResizeHandler() সেটাই করে, শুধু wrapper দরকার হলে display: table-cell ব্যবহার করুন।

একসাথে সব — সংরক্ষণ করে রাখা

এই চারটি preference ইউজারের। ব্রাউজার রিফ্রেশেও থাকা উচিত — নয়তো প্রতিবার সব লুকানো অবস্থা হারিয়ে যাবে।

// localStorage-এ সংরক্ষণ
const STORAGE_KEY = "products-table-layout";

function loadLayout() {
  if (typeof window === "undefined") return {};
  try {
    return JSON.parse(
      localStorage.getItem(STORAGE_KEY) ?? "{}",
    ) as TableLayout;
  } catch {
    return {};
  }
}

type TableLayout = {
  columnVisibility?: VisibilityState;
  columnOrder?: ColumnOrderState;
  columnPinning?: ColumnPinningState;
  columnSizing?: ColumnSizingState;
};

// component-এ
const [layout, setLayout] = useState<TableLayout>(loadLayout);

// বদলালে সংরক্ষণ
useEffect(() => {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(layout));
}, [layout]);
// একসাথে save ও load
const table = useReactTable({
  // …
  onColumnVisibilityChange: setLayout1,
  onColumnOrderChange: setLayout2,
  onColumnPinningChange: setLayout3,
  onColumnSizingChange: setLayout4,

  state: {
    columnVisibility: layout.columnVisibility,
    columnOrder: layout.columnOrder,
    columnPinning: layout.columnPinning,
    columnSizing: layout.columnSizing,
  },
});
layout ডিফল্ট হিসেবে রাখুন

state option-এ খালি ফাঁকা মান ({}, []) দিলে initialState কাজ করে। শুধু table বানিয়ে getColumn("price").hide() বললে localStorage থেকে আসা preference উড়ে যাবে।

রিসেট বাটন

function resetLayout(table: Table<Product>) {
  table.resetColumnVisibility();
  table.resetColumnOrder();
  table.resetColumnPinning();
  table.resetColumnSizing();
}

// ডিফল্ট কিন্তু পরিবর্তিত কোনো কিছু আছে কিনা
const isDirty =
  Object.keys(table.getState().columnVisibility).length > 0 ||
  table.getState().columnOrder.length > 0 ||
  table.getState().columnPinning.left.length > 0 ||
  table.getState().columnPinning.right.length > 0 ||
  Object.keys(table.getState().columnSizing).length > 0;

মোবাইলে ধারণা

ছোট স্ক্রিনে একই টেবিল রাখলে কলামের চেহারা নষ্ট হয়। দুটি সাধারণ সমাধান:

// ১. default visibility অনুযায়ী শুধু ২-৩টি কলাম রাখুন
const [columnVisibility, setColumnVisibility] = useState({
  select: true,
  name: true,
  price: true,
  stock: false,
  createdAt: false,
});

// বড় স্ক্রিনে
useEffect(() => {
  if (window.innerWidth >= 768) setColumnVisibility({});
}, []);

// ২. মোবাইলে পাশ থেকে স্লাইড করে কলাম দেখান
<Sheet>
  <SheetTrigger asChild>
    <Button size="sm" variant="outline">কলাম দেখুন</Button>
  </SheetTrigger>
  <SheetContent side="right" className="w-72">
    {/* একই ColumnVisibility মেনু */}
  </SheetContent>
</Sheet>

সাধারণ বাগ

ঘটনাকারণসমাধান
লুকানো কলামের জায়গা ফাঁকাgetAllCells() ব্যবহারgetVisibleCells() ব্যবহার করুন
sticky কলামের উপর লেখা ভেসে যায়background নেইbg-background দিন
Resize করলে কলাম ফিরে আসেstate দেওয়া হয়নিstate: { columnSizing }
localStorage পড়া যায় নাSSR-এ window নেইtypeof window check
menu-তে "actions" দেখাচ্ছেenableHiding: false নেইদুটোই false দিন
pin করলে কলাম উলটোstate-এ left/right ভুলkey ঠিক "left" / "right"
resize করলে সব সরে যায়resize handle th-র বাইরেposition: relative দিন

সারসংক্ষেপ

  • চারটি স্বাধীন state: columnVisibility, columnOrder, columnPinning, columnSizing
  • লুকালে getVisibleCells() আর render কোড অপরিবর্তিত থাকে
  • sticky-র জন্য bg-background + z-index বাধ্যতামূলক
  • columnResizeMode: "onEnd" বড় টেবিলে দ্রুত
  • preference localStorage-এ রাখলে UX অনেক ভালো

পরবর্তী

এতক্ষণ সব হিসাব browser-এই হচ্ছিল। পরের অধ্যায়ে server-side mode — manual flag, rowCount, আর TanStack Query-এর সাথে সেতু বানানো।

শেষ প্যারায় নিজে practice করুন — কোড কপি করে নিজের এডিটরে চালিয়ে দেখুন।
আগেরRow Selection — row বাছাইপরেরServer-side Mode — সার্ভার থেকে ডেটা
এই পেজে
Tech Tutor

বাংলায় ওয়েব ডেভেলপমেন্ট ডকুমেন্টেশন শিখুন — ধাপে ধাপে।

Homeসব ডকুমেন্টেশনQuick Startআমার নোটস

© 2026 Tech Tutor. বাংলায় শিখুন, বাংলায় গড়ুন।

mdanamolhasan1998@gmail.com