# Token

> Tokens are compact chips that tag or categorise an item, such as a subject or an applied filter, and can be removed.

Source: /docs/components/token

```tsx
import { Token } from '@aksornproject/aksara-ui'
```

## Colours

`color` picks one of eleven colours. `default` is a neutral grey. Pick a colour for its meaning in the product and keep it consistent across a list.

```tsx
import { Token } from '@aksornproject/aksara-ui'

export default function TokenColors() {
  return (
    <>
      <Token>Design</Token>
      <Token color="red">Overdue</Token>
      <Token color="orange">Homework</Token>
      <Token color="yellow">Quiz</Token>
      <Token color="green">Approved</Token>
      <Token color="teal">Science</Token>
      <Token color="cyan">Mathematics</Token>
      <Token color="blue">History</Token>
      <Token color="purple">Art</Token>
      <Token color="pink">Music</Token>
      <Token color="gray">Elective</Token>
    </>
  )
}
```

## Sizes

Three heights, 20, 24 and 28px. `small` is the default. Padding, gap, font and slots stay the same at every size.

```tsx
import { Token } from '@aksornproject/aksara-ui'

export default function TokenSizes() {
  return (
    <>
      <Token color="blue" size="small">
        Small
      </Token>
      <Token color="blue" size="medium">
        Medium
      </Token>
      <Token color="blue" size="large">
        Large
      </Token>
    </>
  )
}
```

## Icons

`leadingIcon` and `trailingIcon` render in 16px boxes and take the text colour. Slot content is decorative, so mark icons `aria-hidden`.

```tsx
import {
  IconFoldersFolderLink,
  IconUserSingleUser,
} from '@aksornproject/aksara-icons'
import { Token } from '@aksornproject/aksara-ui'

export default function TokenIcons() {
  return (
    <>
      <Token color="blue" leadingIcon={<IconUserSingleUser aria-hidden />}>
        Assigned to me
      </Token>
      <Token
        color="purple"
        trailingIcon={<IconFoldersFolderLink aria-hidden />}
      >
        Shared folder
      </Token>
    </>
  )
}
```

## Removable

`onRemove` adds a 20px remove button. It runs on a click, or on Enter or Space. The Token does not hide itself. The caller removes it from its list.

After a removal, move focus to a neighbouring Token or to another control, because the removed Token unmounts. This demo moves focus to the next remove button, then the previous one, then the reset button.

```tsx
import { Token, typography } from '@aksornproject/aksara-ui'
import { useRef, useState, type MouseEvent } from 'react'

const SUBJECTS = ['Design', 'Science', 'Mathematics', 'History', 'Art']

export default function TokenRemovable() {
  const [subjects, setSubjects] = useState(SUBJECTS)
  const list = useRef<HTMLUListElement>(null)
  const reset = useRef<HTMLButtonElement>(null)

  function remove(subject: string) {
    return (event: MouseEvent<HTMLButtonElement>) => {
      const buttons = [...(list.current?.querySelectorAll('button') ?? [])]
      const index = buttons.indexOf(event.currentTarget)
      ;(buttons[index + 1] ?? buttons[index - 1] ?? reset.current)?.focus()
      setSubjects((current) => current.filter((entry) => entry !== subject))
    }
  }

  return (
    <div className="flex flex-col items-center gap-spacing-12">
      <ul
        ref={list}
        aria-label="Subjects"
        className="flex flex-wrap gap-spacing-8"
      >
        {subjects.map((subject) => (
          <Token
            key={subject}
            render={<li />}
            color="cyan"
            size="medium"
            onRemove={remove(subject)}
          >
            {subject}
          </Token>
        ))}
      </ul>
      <button
        ref={reset}
        type="button"
        className={`${typography('h5-medium')} text-color-text-primary underline`}
        onClick={() => setSubjects(SUBJECTS)}
      >
        Reset subjects
      </button>
    </div>
  )
}
```

## Custom remove button

`removeLabel` is the verb read before the label, so `"Delete tag"` gives "Delete tag Field trip". `removeIcon` replaces the X glyph. `removeButtonClassName` merges last into the button, so the app wins.

```tsx
import { IconInterfaceEssentialTrashDeleteBin2 } from '@aksornproject/aksara-icons'
import { Token } from '@aksornproject/aksara-ui'

export default function TokenCustomRemove() {
  return (
    <>
      <Token
        color="blue"
        size="medium"
        removeLabel="Delete tag"
        removeIcon={<IconInterfaceEssentialTrashDeleteBin2 aria-hidden />}
        onRemove={() => {}}
      >
        Field trip
      </Token>
      <Token
        color="pink"
        size="medium"
        removeButtonClassName="bg-color-background-pink"
        onRemove={() => {}}
      >
        Music
      </Token>
    </>
  )
}
```

## Disabled

`disabled` drops the Token to 50% opacity. The remove button becomes a disabled native button and leaves the tab order.

```tsx
import { Token } from '@aksornproject/aksara-ui'

export default function TokenDisabled() {
  return (
    <>
      <Token disabled>Archived</Token>
      <Token disabled size="medium" color="red" onRemove={() => {}}>
        Locked filter
      </Token>
    </>
  )
}
```

## Width and truncation

The Token hugs its content up to the width of its container. The label truncates with an ellipsis when space runs out, and the slots and the remove button keep their size. `className="w-full"` fills the container.

```tsx
import { Token } from '@aksornproject/aksara-ui'

export default function TokenTruncation() {
  return (
    <div className="flex w-spacing-160 flex-col items-start gap-spacing-8">
      <Token color="orange" onRemove={() => {}}>
        Grade 6 science fair project
      </Token>
      <Token color="gray" className="w-full">
        Fill width
      </Token>
    </div>
  )
}
```

## Accessibility

- The Token is a plain `span` with no role. The remove button is the only control.
- The remove button's name is `removeLabel` (default "Remove") followed by the label, such as "Remove Design".
- When two Tokens can share a label, give each a `removeLabel` that tells them apart.
- Never put a link or a button in a slot or as the `render` target, because the Token already holds the remove button.
- The focus ring draws on the whole Token while the remove button has keyboard focus.
- The remove target is 20px, under the 24px WCAG 2.2 target size. Keep 4px or more between removable Tokens so the spacing exception applies.

## API

### Token

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, {}> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `onRemove` | `((event: MouseEvent<HTMLButtonElement>) => void)` |  | Runs when the remove button is clicked, or pressed with Enter or Space. Your app unmounts the Token. |
| `removeLabel` | `string` | `'Remove'` | Read before the label as the remove button's name, so the default gives "Remove Design". Defaults to `'Remove'`. |
| `removeIcon` | `ReactNode` |  | Replaces the default X glyph. Renders in the 16px box and inherits the text color. Hide decorative icons from assistive technology. |
| `removeButtonClassName` | `string` |  | Classes merged last into the remove button, so the app wins. Use this for hover fills or shapes the default 20px circle lacks. |
| `children` (required) | `ReactNode` |  | The label. It truncates with an ellipsis when the Token is too narrow. |
| `size` | `"large" \| "medium" \| "small"` | `'small'` | Height 20, 24 or 28px. Defaults to `'small'`. |
| `color` | `"blue" \| "cyan" \| "default" \| "gray" \| "green" \| "orange" \| "pink" \| "purple" \| "red" \| "teal" \| "yellow"` | `'default'` | Defaults to `'default'`. |
| `disabled` | `boolean` | `false` | Drops the Token to 50% opacity and disables the remove button. |
| `leadingIcon` | `ReactNode` |  | Decorative content in a 16px box before the label. |
| `trailingIcon` | `ReactNode` |  | Decorative content in a 16px box after the label. |
| `className` | `string` |  | Merged last with `cn()`, so these classes win. |