---
name: unity-migrate-from-midnight
description: >
  Load when replacing @payfit/midnight UI with Unity. Use it to choose Unity
  equivalents, avoid obsolete Midnight patterns, and route unclear component
  mappings to unity-find-component.
type: lifecycle
library: '@payfit/unity-components'
library_version: '2.x'
sources:
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/tooltip/Tooltip.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/button/Button.tsx'
  - 'PayFit/hr-apps:AGENTS.md'
---

Convert a legacy `@payfit/midnight` screen to Unity. The table below
covers the highest-traffic mappings; the exhaustive ~85-row map lives
in `references/midnight-component-map.md`. When a Midnight component is
not in either, fall through to `unity-find-component`.

## Equivalency Map (high-traffic)

| Midnight               | Unity                                        | Notes                                                                |
| ---------------------- | -------------------------------------------- | -------------------------------------------------------------------- |
| `Button`               | `Button`                                     | Prop renames apply (see below)                                       |
| `Box`                  | `Flex` (or `Grid` for 2D)                    | No 1:1 layout box; use `Flex` with `direction`/`gap`/`align`         |
| `Modal`                | `Dialog` + `DialogContent` + `DialogActions` | Use `PromoDialog` for marketing dialogs (requires `PromoDialogHero`) |
| `Link`                 | `RawLink` (base) or `Link` (tanstack-router) | Pick the router-aware one when navigating inside the app             |
| `Heading`              | `Text` with `variant="heading*"`             | No dedicated `Heading`; typography is a `Text` variant               |
| `Badge`                | `Badge`                                      | `color` → `variant`; Unity Badge is the chip with intent colors      |
| `Pill`                 | `Pill`                                       | Unity Pill is numeric-only with fewer colors; same indicator intent  |
| `transition('smooth')` | `uy:transition-*` / `uy:duration-*` classes  | No JS helper; use TailwindCSS utilities                              |

For everything else, see `references/midnight-component-map.md`. When a
target is unclear, switch to `unity-find-component` rather than
guessing. Do not invent Unity component names from Midnight ones.

## Prop renames

Confirmed renames from `Button.tsx` and the React Aria base classes Unity
extends:

| Midnight          | Unity            | Mechanism                                              |
| ----------------- | ---------------- | ------------------------------------------------------ |
| `visual="danger"` | `color="danger"` | Color/intent prop renamed on `Button`, `Pill`, `Alert` |
| `onClick={fn}`    | `onPress={fn}`   | React Aria press events (touch/keyboard parity)        |
| `disabled`        | `isDisabled`     | React Aria boolean convention                          |

Do not auto-extend this table. If a prop is not listed, look at the Unity
component source — many props are unchanged and adding speculative
renames produces silent default behavior.

## Accessibility deltas

Unity enforces several WCAG rules that Midnight allowed:

- **Tooltip on disabled control — rejected at the library level.**
  `Tooltip` ignores or removes the trigger when the wrapped control is
  disabled, because disabled controls are not focusable and therefore the
  tooltip is unreachable by keyboard or screen reader. See the
  `## Common Mistakes` entry below for the replacement.
- **Dialog focus management.** Unity `Dialog` traps focus, restores it on
  close, and requires an accessible title. Midnight `Modal` was looser
  about both.
- **Autocomplete keyboard navigation.** Unity `Autocomplete` follows the
  ARIA combobox pattern (Arrow keys, Home/End, Escape to dismiss). If
  Midnight code relied on click-only interactions, those still work, but
  do not strip the keyboard handlers when porting.

## Forms

Midnight-era screens commonly use React Hook Form. When porting, replace
the form with `useTanstackUnityForm` and the Tanstack-bound `*Field`
components (`TextField`, `SelectField`, `NumberField`, etc.). The legacy
RHF `useUnityForm` + `*Field` wrappers in the same package index are
deprecated and will be removed after the rebrand — do not pause on the
RHF intermediate step. See `unity-tanstack-form`.

## Common Mistakes

### CRITICAL Wrap disabled button in Tooltip (Midnight-era pattern)

Wrong:

```tsx
<Tooltip title="Disabled because …">
  <Button isDisabled>Submit</Button>
</Tooltip>
```

Correct:

```tsx
<Button isDisabled>Submit</Button>
<Text variant="bodySmall" color="content.neutral.low">
  Disabled because …
</Text>
// Or, if you must explain inline, gate the tooltip on enabled state:
{isDisabled
  ? <Button isDisabled>Submit</Button>
  : <Tooltip title="…"><Button>Submit</Button></Tooltip>}
```

Midnight allowed `Tooltip` on disabled buttons; Unity enforces the WCAG
rule that disabled controls must not have tooltips because they are not
keyboard-focusable, so the tooltip is unreachable — Unity's `Tooltip`
component rejects this pairing at the library level.

Source: libs/shared/unity/components/src/components/tooltip/Tooltip.tsx; maintainer interview (Unity enforces)

### HIGH Assume Midnight props map 1:1 to Unity

Wrong:

```tsx
<Button visual="danger" onClick={handleDelete}>
  Delete
</Button>
```

Correct:

```tsx
<Button color="danger" onPress={handleDelete}>
  Delete
</Button>
```

`visual` is the Midnight intent prop and `color` is the Unity one; `onClick`
still type-checks on the underlying DOM node but bypasses React Aria's
touch and keyboard press handling, so keyboard activations silently no-op.

Source: libs/shared/unity/components/src/components/button/Button.tsx

### MEDIUM Leave Midnight + Unity side-by-side in the same screen

Wrong:

```tsx
import { Button } from '@payfit/midnight'
import { Dialog } from '@payfit/unity-components'
```

Correct:

```tsx
import { Button, Dialog } from '@payfit/unity-components'
```

Midnight and Unity ship divergent theme tokens — colors, spacing, and
typography differ — so a mixed screen renders with mismatched scales and
the visual regression lands silently because both stylesheets are valid.

Source: AGENTS.md migration awareness; maintainer interview

### MEDIUM Port Midnight transition utility instead of using uy: classes

Wrong:

```tsx
import { transition } from '@payfit/midnight'
<Flex transition={transition('smooth')}>
```

Correct:

```tsx
<Flex className="uy:transition-all uy:duration-200 uy:ease-out">
```

Unity has no JS transition helper; the migration target is TailwindCSS 4
transition utilities under the `uy:` prefix, and porting the Midnight
function leaves a dead import that the new theme tokens never honor.

Source: no Unity equivalent for Midnight transition helpers

## See also

- `references/midnight-component-map.md` — exhaustive Midnight → Unity
  map (v1 + v2 surfaces, ~85 rows, grouped by category)
- `unity-find-component` — decision tree for any Midnight component
  still ambiguous after consulting the full map
- `unity-overlays` — full a11y constraints for `Dialog`, `Tooltip`,
  `Popover`, `Menu`
- `unity-tanstack-form` — replacement path for RHF forms in the migrated
  screen
