voting_chip

Chip

Compact labels for filters, tags, and actions.

Chip Examples

Copy-ready examples with live previews.

Basic usage

Simple label/tag. Combine variant + style + size for the look you need.

Live preview

Basic

import Chip from "@/_components/chip/Chip";

export default function Example() {
  return <Chip label="Design system" variant="primary" style="filled" size="medium" />;
}

Variants

Variant controls the color tone (primary/secondary/tertiary/etc).

Live preview

All variants

import Chip from "@/packages/ui/src/_components/chip/Chip";

const variants = ["primary","secondary","tertiary","success","warning","danger","info"] as const;

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      {variants.map((v) => (
        <Chip key={v} label={v} variant={v} style="filled" size="medium" />
      ))}
    </div>
  );
}

Styles

Style controls the surface: "filled", "outlined", or "elevated".

Live preview

filled / outlined / elevated

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Primary" variant="primary" style="filled" />
        <Chip label="Secondary" variant="secondary" style="filled" />
        <Chip label="Tertiary" variant="tertiary" style="filled" />
      </div>

      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Primary" variant="primary" style="outlined" />
        <Chip label="Secondary" variant="secondary" style="outlined" />
        <Chip label="Tertiary" variant="tertiary" style="outlined" />
      </div>

      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Primary" variant="primary" style="elevated" />
        <Chip label="Secondary" variant="secondary" style="elevated" />
        <Chip label="Tertiary" variant="tertiary" style="elevated" />
      </div>
    </div>
  );
}

Sizes

Size controls the paddings/typography scale.

Live preview

small / medium / large

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      <Chip label="small" variant="secondary" style="outlined" size="small" />
      <Chip label="medium" variant="secondary" style="outlined" size="medium" />
      <Chip label="large" variant="secondary" style="outlined" size="large" />
    </div>
  );
}

Rounded presets

roundedSize applies a radius preset.

Live preview

roundedSize

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      <Chip label="Rounded sm" variant="primary" style="filled" roundedSize="small" />
      <Chip label="Rounded md" variant="primary" style="filled" roundedSize="medium" />
      <Chip label="Rounded lg" variant="primary" style="filled" roundedSize="large" />
    </div>
  );
}

Icons

Optional leading icon (iconName) and optional close affordance (iconClose).

iconClose is currently a visual affordance only (no click handler wired by the component).

Current implementation always renders the close icon with name="close" and ignores the provided iconClose value.

Live preview

Leading icon

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      <Chip label="Filters" variant="secondary" style="outlined" iconName="Tune" />
      <Chip label="Starred" variant="warning" style="filled" iconName="Star" />
      <Chip label="Verified" variant="success" style="elevated" iconName="Check" />
    </div>
  );
}

Live preview

Close affordance (visual only)

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      <Chip label="Closable (visual)" variant="secondary" style="outlined" iconClose="true" />
      <Chip label="With icon + close" variant="primary" style="filled" iconName="Label" iconClose="true" />
      <Chip label="Danger + close" variant="danger" style="filled" iconClose="1" />
    </div>
  );
}

Icons (extended set)

Bigger set of icon chips across styles to validate spacing/alignment.

Live preview

Filled
Outlined
Elevated

Icon chips grid

import { useMemo } from "react";
import Chip from "@/_components/chip/Chip";

type Row = {
  title: string;
  style: "filled" | "outlined" | "elevated";
  items: Array<{ id: string; label: string; iconName: string; variant: "primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info" }>;
};

export default function Example() {
  const rows = useMemo<Row[]>(
    () => [
      {
        title: "Filled",
        style: "filled",
        items: [
          { id: "wifi", label: "Wi-Fi", iconName: "wifi", variant: "primary" },
          { id: "bluetooth", label: "Bluetooth", iconName: "bluetooth", variant: "secondary" },
          { id: "airplane", label: "Airplane", iconName: "flight", variant: "info" },
          { id: "warning", label: "Warnings", iconName: "warning", variant: "warning" },
          { id: "danger", label: "Errors", iconName: "report", variant: "danger" },
          { id: "success", label: "Done", iconName: "task_alt", variant: "success" },
        ],
      },
      {
        title: "Outlined",
        style: "outlined",
        items: [
          { id: "search", label: "Search", iconName: "search", variant: "secondary" },
          { id: "filter", label: "Filter", iconName: "tune", variant: "tertiary" },
          { id: "bookmark", label: "Saved", iconName: "bookmark", variant: "primary" },
          { id: "info", label: "Info", iconName: "info", variant: "info" },
          { id: "bell", label: "Alerts", iconName: "notifications", variant: "warning" },
          { id: "bug", label: "Bugs", iconName: "bug_report", variant: "danger" },
        ],
      },
      {
        title: "Elevated",
        style: "elevated",
        items: [
          { id: "user", label: "Profile", iconName: "person", variant: "primary" },
          { id: "settings", label: "Settings", iconName: "settings", variant: "secondary" },
          { id: "image", label: "Photos", iconName: "image", variant: "tertiary" },
          { id: "lock", label: "Security", iconName: "lock", variant: "info" },
          { id: "star", label: "Featured", iconName: "star", variant: "warning" },
          { id: "check", label: "Verified", iconName: "check_circle", variant: "success" },
        ],
      },
    ],
    []
  );

  return (
    <div style={{ display: "grid", gap: 12 }}>
      {rows.map((row) => (
        <div key={row.title} style={{ display: "grid", gap: 10, padding: 14, border: "1px solid rgba(0,0,0,0.12)", borderRadius: 12 }}>
          <div style={{ fontWeight: 700 }}>{row.title}</div>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 12, alignItems: "center" }}>
            {row.items.map((it) => (
              <Chip key={it.id} label={it.label} iconName={it.iconName} variant={it.variant} style={row.style} size="medium" />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

States

Visual states (selected/dragged/disabled).

Live preview

State combinations

import Chip from "@/packages/ui/src/_components/chip/Chip";

export default function Example() {
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Default" variant="primary" style="filled" />
        <Chip label="Selected" variant="primary" style="filled" selected />
        <Chip label="Dragged" variant="primary" style="filled" dragged />
        <Chip label="Disabled" variant="primary" style="filled" disabled />
      </div>

      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Selected + outlined" variant="secondary" style="outlined" selected />
        <Chip label="Dragged + outlined" variant="secondary" style="outlined" dragged />
        <Chip label="Disabled + outlined" variant="secondary" style="outlined" disabled />
      </div>

      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        <Chip label="Selected + elevated" variant="tertiary" style="elevated" selected />
        <Chip label="Dragged + elevated" variant="tertiary" style="elevated" dragged />
        <Chip label="Disabled + elevated" variant="tertiary" style="elevated" disabled />
      </div>
    </div>
  );
}

Controlled selection (wrapper pattern)

Chip is currently presentational (doesn't forward onClick/onChange). This is a minimal workaround to toggle selected state.

This wrapper pattern is only for demos/playgrounds. Prefer adding native onClick/disabled support inside Chip when you need real interaction.

Because Chip renders a <button> without type="button", avoid placing it inside a form unless you control submit behavior.

Live preview

Selected: design, ux

Wrapper click toggles selected

import { useMemo, useState } from "react";
import Chip from "@/packages/ui/src/_components/chip/Chip";

type Item = { id: string; label: string };

export default function Example() {
  const [selected, setSelected] = useState<string[]>(["design", "ux"]);

  const items = useMemo<Item[]>(
    () => [
      { id: "design", label: "Design" },
      { id: "ux", label: "UX" },
      { id: "frontend", label: "Frontend" },
      { id: "backend", label: "Backend" },
      { id: "research", label: "Research" },
      { id: "testing", label: "Testing" },
    ],
    []
  );

  const toggle = (id: string) =>
    setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div>Selected: {selected.length ? selected.join(", ") : "(none)"}</div>

      <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
        {items.map((it) => (
          <div
            key={it.id}
            style={{ display: "inline-flex" }}
            onClick={(e) => {
              e.preventDefault();
              toggle(it.id);
            }}
          >
            <Chip label={it.label} variant="secondary" style="outlined" selected={selected.includes(it.id)} />
          </div>
        ))}
      </div>
    </div>
  );
}

Stress test (many chips)

Useful to validate wrapping, spacing, and line breaks.

Live preview

Many chips

import { useMemo } from "react";
import Chip from "@/packages/ui/src/_components/chip/Chip";

const variants = ["primary","secondary","tertiary","success","warning","danger","info"] as const;

export default function Example() {
  const tags = useMemo(
    () => Array.from({ length: 20 }).map((_, i) => ({
      id: `t_${i + 1}`,
      label: `Tag ${i + 1}`,
      variant: variants[i % variants.length],
    })),
    []
  );

  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
      {tags.map((t) => (
        <Chip key={t.id} label={t.label} variant={t.variant} style="filled" size="small" />
      ))}
    </div>
  );
}

Inside a form

Chip renders a <button> without type='button'. Prevent accidental submits in demos.

Live preview

Submitted: no

Prevent submit

import { useState } from "react";
import Chip from "@/packages/ui/src/_components/chip/Chip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [submitted, setSubmitted] = useState(false);

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div>Submitted: {submitted ? "yes" : "no"}</div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          setSubmitted(true);
        }}
        style={{ display: "grid", gap: 12 }}
      >
        <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
          <Chip label="Inside form" variant="primary" style="filled" />
          <Chip label="Outlined" variant="secondary" style="outlined" />
          <Chip label="Disabled" variant="secondary" style="outlined" disabled />
        </div>

        <Button variant="secondary" ripple outline size="small" onClick={() => setSubmitted(false)}>
          Reset state
        </Button>
      </form>
    </div>
  );
}

On this page

Chip Props

Chip — Props
PropTypeRequiredDefaultDescription
labelstringNoText content rendered inside the chip label.
variant"primary" | "secondary" | "tertiary" | "success" | "warning" | "danger" | "info"NoVisual tone (maps to cssnt-chip--*).
size"small" | "medium" | "large"NoSize preset (maps to cssnt-chip--sm/md/lg).
style"outlined" | "filled" | "elevated"NoSurface style preset.
roundedSize"small" | "medium" | "large"NoBorder radius preset (maps to cssnt-chip--rounded-*).
disabledbooleanNofalseAdds is-disabled class (visual state). Does not forward the native disabled attribute yet.
selectedbooleanNofalseAdds is-selected class (visual state).
draggedbooleanNofalseAdds is-dragged class (visual state).
iconNamestringNoOptional leading icon name (renders <Icon name={iconName} size='small' />).
iconClosestringNoIf truthy, shows a trailing close affordance. Current code always renders name='close' and ignores the provided value.
onclick(event: React.MouseEvent<HTMLDivElement>) => voidNoundefinedDeclared in the type but not used by the component yet.
onchange(event: React.ChangeEvent<HTMLInputElement>) => voidNoundefinedDeclared in the type but not used by the component yet.