# Example: Replacing a component

The grid's own tool panel and filter dialog, with every Button replaced by the app's — one slot, applied everywhere, branching on site for the one place it treats differently.

Source: https://pretable.ai/examples/components-button.md

```tsx ComponentsGrid.tsx
"use client";

import { PretableSurface } from "@pretable/react";

import "./brand-button.css";

import { BrandButton } from "./BrandButton";
import { columns } from "./columns";
import { trades, type Trade } from "./data";

const VIEWPORT_HEIGHT = 260;

export function ComponentsGrid() {
  return (
    <div>
      <p style={{ margin: "0 0 8px", fontSize: 13 }}>
        Every labelled button below is <code>BrandButton</code>; the icon
        buttons — the funnels, the ⋮ menus — are still pretable&apos;s, because
        this demo replaces only <code>Button</code>. Reset columns is the
        app&apos;s danger button (it branches on <code>site</code>); the Filters
        section&apos;s <code>+ filter</code> / <code>+ group</code> are its
        ghost buttons; a column funnel&apos;s dialog has its link-styled{" "}
        <code>Clear</code>.
      </p>
      <PretableSurface<Trade>
        ariaLabel="Trades"
        columns={columns}
        components={{ Button: BrandButton }}
        getRowId={(row) => row.id}
        rows={trades}
        toolPanel={{ defaultActiveSection: "columns" }}
        viewportHeight={VIEWPORT_HEIGHT}
      />
    </div>
  );
}
```

```tsx BrandButton.tsx
"use client";

import { forwardRef } from "react";
import type { PretableButtonProps } from "@pretable/react";

/**
 * The app's own button, standing in for every Button the grid renders. It
 * receives exactly what pretable's does — `site` included — and forwards its
 * ref, which is the one thing the grid asks of a replacement: menus anchor on
 * the node, and focus returns to it.
 */
export const BrandButton = forwardRef<HTMLButtonElement, PretableButtonProps>(
  function BrandButton({ site, variant, className, ...props }, ref) {
    // One place treated differently: the reset is destructive, so it gets the
    // app's danger styling. Everything else is the brand default.
    const tone = site === "tool-reset" ? "danger" : (variant ?? "ghost");
    return (
      <button
        {...props}
        ref={ref}
        type="button"
        className={["brand-button", `brand-button--${tone}`, className]
          .filter(Boolean)
          .join(" ")}
      />
    );
  },
);
```

```css brand-button.css
.brand-button {
  border: 0;
  border-radius: 999px;
  padding: 0 12px;
  height: 26px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.brand-button--ghost {
  background: #eef2ff;
  color: #3730a3;
}

.brand-button--link {
  background: transparent;
  color: #3730a3;
  padding: 0 4px;
}

.brand-button--danger {
  background: #fee2e2;
  color: #991b1b;
}

.brand-button:disabled {
  opacity: 0.5;
  cursor: default;
}
```

```ts columns.ts
import type { PretableColumn } from "@pretable/react";

import type { Trade } from "./data";

export const columns: PretableColumn<Trade>[] = [
  { id: "symbol", header: "Symbol", widthPx: 110, type: "text" },
  { id: "side", header: "Side", widthPx: 90, type: "enum" },
  { id: "qty", header: "Qty", widthPx: 90, type: "number" },
  { id: "price", header: "Price", widthPx: 110, type: "number" },
];
```

```ts data.ts
export interface Trade {
  id: string;
  symbol: string;
  side: "Buy" | "Sell";
  qty: number;
  price: number;
}

export const trades: Trade[] = [
  { id: "t1", symbol: "AAPL", side: "Buy", qty: 100, price: 226.11 },
  { id: "t2", symbol: "MSFT", side: "Sell", qty: 40, price: 418.38 },
  { id: "t3", symbol: "NVDA", side: "Buy", qty: 25, price: 869.63 },
  { id: "t4", symbol: "AMZN", side: "Buy", qty: 60, price: 183.91 },
  { id: "t5", symbol: "META", side: "Sell", qty: 15, price: 509.62 },
];
```
