---
name: table
description: Guide for implementing tables with inline editing. CRITICAL - Editable cells MUST use Select with variant="ghost". Never wrap badges/other elements.
color: blue
---

# Table Implementation Guide

## 🚨 MANDATORY: Always Use Ghost Variant for Inline Editing

**When using Select for inline editing in tables, you MUST use `variant="ghost"`:**

```tsx
// ✅ CORRECT - Always use ghost variant in tables
<SelectTrigger variant="ghost" className="w-40">
  <SelectValue />
</SelectTrigger>

// ❌ WRONG - Never omit variant in tables
<SelectTrigger className="w-40">
  <SelectValue />
</SelectTrigger>
```

**Why ghost variant?**

- Transparent background blends with table design
- No visible border until hover/focus
- Clean, professional appearance

**Remember: `variant="ghost"` is MANDATORY for ALL table Select components.**

---

## Inline Editing Pattern

### Read-Only Columns

Use plain text, badges, or formatted values for columns that cannot be edited:

```tsx
// Plain text (default)
{
  accessorKey: "assignee",
  header: "Assignee"
}

// Badge for status
{
  accessorKey: "status",
  header: "Status",
  cell: ({ row }) => <Badge>{row.getValue("status")}</Badge>
}

// Formatted date
{
  accessorKey: "dueDate",
  header: "Due Date",
  cell: ({ row }) => {
    const date = new Date(row.getValue("dueDate"));
    return date.toLocaleDateString();
  }
}
```

### Editable Columns

**🚨 CRITICAL: Use Select with `variant="ghost"` for inline editing**

```tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@glide/glide-ui/select";

// Editable status column - MUST use variant="ghost"
{
  accessorKey: "status",
  header: "Status",
  cell: ({ row }) => (
    <Select
      value={row.original.status}
      onValueChange={(value) => {
        updateStatus(row.original.id, value);
      }}
    >
      <SelectTrigger variant="ghost" className="w-40">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="Pending">Pending</SelectItem>
        <SelectItem value="In Progress">In Progress</SelectItem>
        <SelectItem value="Ready">Ready</SelectItem>
      </SelectContent>
    </Select>
  )
}
```

### 🚨 NEVER Mix Badge with Select

```tsx
// ❌ WRONG - Never wrap Badge in Select
{
  accessorKey: "status",
  cell: ({ row }) => (
    <Select value={row.original.status}>
      <Badge>{row.original.status}</Badge>  {/* ❌ NEVER! */}
    </Select>
  )
}

// ✅ CORRECT - Choose ONE:
// Option 1: Read-only (just Badge, no Select)
{
  accessorKey: "status",
  cell: ({ row }) => <Badge>{row.original.status}</Badge>
}

// Option 2: Editable (just Select with ghost variant, no Badge)
{
  accessorKey: "status",
  cell: ({ row }) => (
    <Select
      value={row.original.status}
      onValueChange={(value) => updateStatus(row.original.id, value)}
    >
      <SelectTrigger variant="ghost" className="w-40">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="active">Active</SelectItem>
        <SelectItem value="inactive">Inactive</SelectItem>
      </SelectContent>
    </Select>
  )
}
```

## Complete Example

```tsx
import { useState } from "react";
import { DataTable } from "@glide/glide-ui/data-table";
import { ColumnDef } from "@tanstack/react-table";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@glide/glide-ui/select";

type TaskStatus = "Pending" | "In Progress" | "Ready" | "Picked Up";

type Task = {
  id: number;
  name: string;
  assignee: string;
  status: TaskStatus;
  dueDate: string;
};

function TaskTable() {
  const [data, setData] = useState<Task[]>(sampleTasks);

  const updateStatus = (id: number, newStatus: TaskStatus) => {
    setData((prevData) =>
      prevData.map((task) =>
        task.id === id ? { ...task, status: newStatus } : task,
      ),
    );
  };

  const columns: ColumnDef<Task>[] = [
    {
      accessorKey: "name",
      header: "Task",
      cell: ({ row }) => (
        <div className="font-medium">{row.getValue("name")}</div>
      ),
    },
    {
      accessorKey: "assignee",
      header: "Assignee",
    },
    {
      accessorKey: "status",
      header: "Status",
      cell: ({ row }) => (
        <Select
          value={row.original.status}
          onValueChange={(value) => {
            updateStatus(row.original.id, value as TaskStatus);
          }}
        >
          <SelectTrigger variant="ghost" className="w-40">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="Pending">Pending</SelectItem>
            <SelectItem value="In Progress">In Progress</SelectItem>
            <SelectItem value="Ready">Ready</SelectItem>
            <SelectItem value="Picked Up">Picked Up</SelectItem>
          </SelectContent>
        </Select>
      ),
    },
    {
      accessorKey: "dueDate",
      header: "Due Date",
      cell: ({ row }) => {
        const date = new Date(row.getValue("dueDate"));
        return date.toLocaleDateString();
      },
    },
  ];

  return <DataTable columns={columns} data={data} />;
}
```

## Key Rules

- **Read-only columns**: Use Badge, plain text, or formatted text
- **Editable columns**: Use Select with `variant="ghost"` ONLY (no Badge, no wrapping)
- **NEVER combine**: Badge and Select together is ALWAYS wrong
- **ALWAYS use `variant="ghost"`**: Mandatory for all table Select components
- **State management**: Use `useState` to track editable data
- **Update function**: Create typed function to update specific rows

**🚨 CRITICAL REMINDER: Every SelectTrigger in a table MUST have `variant="ghost"`.**
