# TypeScript

The types that ferry-ui exports, and how to use them in your own components.

The package contains its type declarations. Import each type from the package root, with the components.

```ts

```

## Prop types

A component that adds props of its own exports a prop type: `ButtonProps`, `CalloutProps`, `FieldProps`. Use this type to make a component that extends a ferry-ui component.

```tsx
import { Button, type ButtonProps } from 'ferry-ui'
import { Download } from 'lucide-react'

// Each prop of Button, but this component sets the icon and the text.
type ExportButtonProps = Omit<ButtonProps, 'icon' | 'children'> & {
  format: 'CSV' | 'PDF'
}

function ExportButton({ format, ...props }: ExportButtonProps) {
  return (
    <Button icon={<Download />} {...props}>
      Export {format}
    </Button>
  )
}

export default function PropTypes() {
  return (
    <>
      <ExportButton format="CSV" />
      <ExportButton format="PDF" variant="outline" />
    </>
  )
}
```

## Components with no prop type

Some components only pass the props of their Radix part or of their HTML element. They export no prop type. [Checkbox](/docs/components/checkbox), [Label](/docs/components/label) and the parts of [Card](/docs/components/card) are examples.

Read the props of these components with `ComponentProps` from React.

```tsx
import type { ComponentProps } from 'react'
import { Checkbox, Label } from 'ferry-ui'

// Checkbox exports no prop type: read the props from the component.
type OptionProps = ComponentProps<typeof Checkbox> & {
  label: string
}

function Option({ label, ...props }: OptionProps) {
  return (
    <Label>
      <Checkbox {...props} />
      {label}
    </Label>
  )
}

export default function ComponentPropsType() {
  return (
    <div className="flex flex-col gap-3">
      <Option label="Send me the weekly digest" defaultChecked />
      <Option label="Send me each invoice" />
    </div>
  )
}
```

## Value types

Some props accept a closed list of values. ferry-ui exports a type for many of these lists.

| Type | Values |
| --- | --- |
| `StatusTone` | `success`, `warning`, `destructive`, `info`, `neutral` |
| `CalloutTone` | `info`, `warning`, `destructive`, `success`, `neutral` |
| `ConfirmDialogTone` | `destructive`, `warning`, `primary` |
| `ThemePreference` | `light`, `dark`, `system` |
| `ResolvedTheme` | `light`, `dark` |

Use these types to connect the values of your domain to ferry-ui in one place. This example gives one tone to each status of an invoice.

```tsx
import { StatusBadge, type StatusTone } from 'ferry-ui'

type InvoiceStatus = 'paid' | 'open' | 'overdue'

// TypeScript refuses a tone that ferry-ui does not have, and a status with no entry.
const INVOICE_STATUS: Record<InvoiceStatus, { tone: StatusTone; label: string }> = {
  paid: { tone: 'success', label: 'Paid' },
  open: { tone: 'info', label: 'Open' },
  overdue: { tone: 'destructive', label: 'Overdue' },
}

const STATUSES: InvoiceStatus[] = ['paid', 'open', 'overdue']

export default function StatusMap() {
  return (
    <>
      {STATUSES.map((status) => (
        <StatusBadge key={status} {...INVOICE_STATUS[status]} />
      ))}
    </>
  )
}
```

## Generic values

`RadioCardOption` and [Radio Card Group](/docs/components/radio-card-group) take the type of the value as a type parameter. The `onValueChange` callback then gets this type, not a `string`.

```tsx
import * as React from 'react'
import { RadioCardGroup, type RadioCardOption } from 'ferry-ui'
import { Globe, Lock } from 'lucide-react'

type Visibility = 'private' | 'public'

const OPTIONS: RadioCardOption<Visibility>[] = [
  { value: 'private', label: 'Private', description: 'Only invited members can open it.', icon: <Lock /> },
  { value: 'public', label: 'Public', description: 'Anyone with the link can view it.', icon: <Globe /> },
]

export default function GenericValue() {
  // `onValueChange` gives a `Visibility`, not a `string`.
  const [visibility, setVisibility] = React.useState<Visibility>('private')

  return (
    <RadioCardGroup
      aria-label="Visibility"
      className="w-full max-w-lg"
      columns={2}
      options={OPTIONS}
      value={visibility}
      onValueChange={setVisibility}
    />
  )
}
```

## Variant helpers

A variant helper returns the classes of a component, for an element that is not this component. Each option has the type of the prop with the same name.

| Helper | Options |
| --- | --- |
| `buttonVariants` | `variant`, `size`, `shape` |
| `badgeVariants` | `variant`, `font`, `shape`, `case` |
| `inputVariants` | `size`, `mono` |
| `tabsListVariants` | `variant` |
| `toggleVariants` | `variant`, `size` |
| `statusBadgeVariants` | `tone`, `size` |
| `iconBoxVariants` | `size`, `tone`, `elevated` |

```tsx
import { buttonVariants, type ButtonProps } from 'ferry-ui'

// The options of the helper have the same types as the props of Button.
type RepositoryLinkProps = Pick<ButtonProps, 'variant' | 'size'>

function RepositoryLink({ variant, size }: RepositoryLinkProps) {
  return (
    <a
      className={buttonVariants({ variant, size })}
      href="https://github.com/Carter2307/ferry-ui"
      target="_blank"
      rel="noreferrer"
    >
      Open the repository
    </a>
  )
}

export default function VariantHelper() {
  return (
    <>
      <RepositoryLink />
      <RepositoryLink variant="outline" size="md" />
    </>
  )
}
```

To get the type of one option, read it from the prop type: `ButtonProps['variant']`. For `iconBoxVariants`, ferry-ui exports the `IconBoxVariantProps` type.

ferry-ui makes these helpers with `cva` from `class-variance-authority`. The `VariantProps` type of that package gives all the options of one helper. Add the package to the dependencies of your app before you import it.

```ts

type BadgeOptions = VariantProps<typeof badgeVariants>
```

Call a variant helper only from a client component. The [Server Components](/docs/handbook/server-components) page gives the reason.

## Navigation types

`NavItem` and `NavGroup` describe the navigation of your app. [Icon Rail](/docs/components/icon-rail), [Mobile Nav](/docs/components/mobile-nav), [Inner Menu](/docs/components/inner-menu) and [Command Menu](/docs/components/command-menu) accept the same value.

```tsx title="navigation.tsx"

  {
    id: 'main',
    items: [
      { id: 'projects', label: 'Projects', icon: <FolderKanban />, href: '/projects', active: true },
      { id: 'settings', label: 'Settings', icon: <Settings />, href: '/settings' },
    ],
  },
]
```

## The link component

`LinkComponent` is the type of a router adapter. The adapter gets `href` as a string, and each prop of an anchor.

```tsx title="router-link.tsx"

```

A router with typed routes can refuse a plain string. If it does, cast `href` in the adapter. The [Routing](/docs/handbook/routing) page shows the adapters for other routers.
