---
name: unity-layout-and-styling
description: >
  Load when composing Unity layouts or styling with uy: Tailwind utilities.
  Use it for Flex/Grid/Text choices, class merging/variants, and validating
  Unity token names before adding styles.
type: core
library: '@payfit/unity-components, @payfit/unity-themes'
library_version: '2.x'
sources:
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/flex/Flex.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/grid/Grid.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/components/src/components/text/Text.tsx'
  - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-merge.ts'
  - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/tailwind-variants.ts'
  - 'PayFit/hr-apps:libs/shared/unity/themes/src/utils/cn.ts'
  - 'PayFit/hr-apps:libs/shared/unity/themes/src/scripts/build.ts'
---

Layout primitives, the `uy:` utility-class system, and the variant/merge tools
used inside `@payfit/unity-components`.

## Setup

```tsx
import { Card, Flex, Grid, Text } from '@payfit/unity-components'

export function PayslipSummary() {
  return (
    <Card>
      <Flex direction="col" gap="200" className="uy:p-300">
        <Text variant="h3" asElement="h2">
          Payslip
        </Text>
        <Grid cols={12} className="uy:gap-200 uy:md:gap-300">
          <Flex direction="col" className="uy:col-span-12 uy:md:col-span-6">
            <Text variant="overline">Gross</Text>
            <Text variant="bodyLargeStrong">€ 4,200.00</Text>
          </Flex>
          <Flex direction="col" className="uy:col-span-12 uy:md:col-span-6">
            <Text variant="overline">Net</Text>
            <Text variant="bodyLargeStrong">€ 3,150.00</Text>
          </Flex>
        </Grid>
      </Flex>
    </Card>
  )
}
```

## Core Patterns

### Flex for 1D, Grid for 2D

`Flex` is for one-dimensional rows/columns; `Grid` is for the 12-column (or 6-column)
two-dimensional layout. Each exposes layout props; everything else goes via
`className` with `uy:` utilities.

```tsx
import { Flex, FlexItem, Grid, GridItem } from '@payfit/unity-components'

// Flex props: asElement, inline, direction, isReversed, wrap,
//             gap, gapX, gapY, justify, align, alignContent, className
<Flex direction="row" gap="200" justify="between" align="center">
  <FlexItem grow="1">Left</FlexItem>
  <FlexItem>Right</FlexItem>
</Flex>

// Grid props: asElement, inline, cols (6 | 12), rows, areas, flow,
//             justifyItems, alignItems, className
// GridItem positions via colSpan/colStart/colEnd OR area (mutually exclusive)
<Grid cols={12} className="uy:gap-200">
  <GridItem colSpan={8}>Main</GridItem>
  <GridItem colSpan={4}>Aside</GridItem>
</Grid>
```

### Responsive classes via uy:md:

There is no responsive prop-object API. Responsive behavior is driven by
TailwindCSS v4 modifiers on `className`.

```tsx
<Flex gap="100" className="uy:md:gap-200 uy:lg:gap-300">
  <span>Item</span>
</Flex>

<Grid cols={12} className="uy:grid-cols-1 uy:md:grid-cols-2 uy:lg:grid-cols-3" />
```

### Variants with uyTv

`uyTv` from `@payfit/unity-themes` is the pre-configured tailwind-variants
factory. It applies the Unity `twMergeConfig` so variant collisions resolve
against Unity tokens. Export the variant function and derive its typed props
with `VariantProps`.

```tsx
import type { VariantProps } from '@payfit/unity-themes'

import { uyTv } from '@payfit/unity-themes'

export const callout = uyTv({
  base: 'uy:inline-flex uy:items-center uy:gap-100 uy:rounded-100 uy:px-200 uy:py-100',
  variants: {
    intent: {
      info: 'uy:bg-surface-primary-default uy:text-content-inverted-default',
      danger: 'uy:bg-surface-danger-default uy:text-content-inverted-default',
      neutral: 'uy:bg-surface-neutral-default uy:text-content-neutral-default',
    },
    size: {
      sm: 'uy:typography-body-small',
      md: 'uy:typography-body',
    },
  },
  defaultVariants: { intent: 'info', size: 'md' },
})

export type CalloutVariantProps = VariantProps<typeof callout>
```

### Class merging with uyMerge

`uyMerge` is `tailwind-merge` configured with Unity's class groups. Use it
whenever an external `className` may collide with internal classes.

```tsx
import { uyMerge } from '@payfit/unity-themes'

uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200'
uyMerge('uy:bg-surface-primary-default', 'uy:bg-surface-danger-default')
// → 'uy:bg-surface-danger-default'
uyMerge('uy:p-100', 'uy:px-200', 'uy:py-300') // p, px, py don't collide
```

### Conditional classes with cn / classNames / clsx

`cn`, `classNames`, and `clsx` are aliases for the same Unity-configured helper
in `@payfit/unity-themes`. Use them for ad-hoc conditional strings — not for
component-scoped variant APIs.

```tsx
import { cn } from '@payfit/unity-themes'

function Row({
  isActive,
  className,
}: {
  isActive: boolean
  className?: string
}) {
  return (
    <div
      className={cn(
        'uy:flex uy:items-center uy:px-200 uy:py-100',
        isActive && 'uy:bg-surface-primary-default',
        className,
      )}
    />
  )
}
```

### Typography with `<Text>`

Use `<Text variant=...>` instead of a `<div>` + typography class. `Text`
picks a semantic element from the variant (e.g. `variant="h1"` → `<h1>`),
applies the typography variant, and exposes `color`, `isTruncated`,
`lineClamp`, and `maxWidthCh`.

```tsx
import { Text } from '@payfit/unity-components'

<Text variant="h1" color="content.primary">Title</Text>
<Text variant="body" color="content.neutral">Description</Text>
// Override the semantic element when needed:
<Text variant="h1" asElement="h2">Visual h1, semantic h2</Text>
```

### Data-attribute pseudo-states

Several Unity components expose internal state via `data-hovered`,
`data-pressed`, `data-focus-visible`, etc. Target the component-owned state
attribute rather than the native CSS pseudo-state.

```tsx
import { ListViewItem } from '@payfit/unity-components'
;<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" />
```

## Common Mistakes

### CRITICAL Use bare Tailwind classes without uy: prefix

Wrong:

```tsx
<Flex className="flex gap-4 p-3"> … </Flex>
```

Correct:

```tsx
<Flex gap="100" className="uy:p-300">
  {' '}
  …{' '}
</Flex>
```

Unity CSS is built with `prefix(uy)`; bare classes are not in the compiled stylesheet and produce no styling.

Source: themes/src/scripts/build.ts:298 (prefix(uy) import)

### HIGH Pass responsive prop objects (v0.x style)

Wrong:

```tsx
<Flex gap={{ initial: '100', md: '200' }} />
```

Correct:

```tsx
<Flex gap="100" className="uy:md:gap-200" />
```

v1.x removed the responsive prop-object API. Use className with uy:md: et al.

Source: components/flex/Flex.tsx:33; themes/docs/files/MIGRATION-v1.md

### HIGH Import twMerge from tailwind-merge directly

Wrong:

```tsx
import { twMerge } from 'tailwind-merge'

twMerge('uy:p-100', 'uy:p-200')
```

Correct:

```tsx
import { uyMerge } from '@payfit/unity-themes'

uyMerge('uy:p-100', 'uy:p-200') // → 'uy:p-200'
```

The unconfigured twMerge has no knowledge of Unity tokens; class conflicts on uy:bg-surface-primary-default vs uy:bg-surface-danger-default are not resolved.

Source: themes/src/utils/tailwind-merge.ts:1-7,75-77

### HIGH Import tv from tailwind-variants directly

Wrong:

```tsx
import { tv } from 'tailwind-variants'
export const button = tv({ base: 'uy:px-200', variants: {...} })
```

Correct:

```tsx
import { uyTv } from '@payfit/unity-themes'
export const button = uyTv({ base: 'uy:px-200', variants: {...} })
```

tv() is unconfigured; uyTv pre-applies the Unity twMergeConfig so variant conflict resolution understands Unity tokens.

Source: themes/src/utils/tailwind-variants.ts:48-51

### MEDIUM Use <div> + typography class instead of <Text>

Wrong:

```tsx
<div className="uy:typography-h1 uy:text-content-primary">Title</div>
```

Correct:

```tsx
<Text variant="h1" color="content.primary">
  Title
</Text>
```

`<Text variant="h1">` auto-selects the correct semantic element (h1) and applies the Unity typography variant; `<div>` loses the semantics and the variant API.

Source: components/text/Text.tsx:60-104,137-139

### MEDIUM Use uy:hover: when component exposes data-\* state

Wrong:

```tsx
<ListViewItem className="uy:hover:bg-surface-primary-hover" />
```

Correct:

```tsx
<ListViewItem className="uy:data-[hovered=true]:bg-surface-primary-hover" />
```

Some Unity components manage state via data-hovered, data-selected, etc. `uy:data-[hovered=true]:` targets the component-managed state and avoids drift.

Source: themes/src/scripts/build.ts:303-307 (custom-variant for data attrs)

### HIGH Hallucinate token names that look plausible but do not exist

Wrong:

```tsx
<div className="uy:bg-primary-500 uy:text-gray-900 uy:border-blue-600" />
```

Correct:

```tsx
// Use Unity's semantic token names (verify against the live class index
// in themes docs or the @theme block in dist/css/unity.css):
<div className="uy:bg-surface-primary-default uy:text-content-primary uy:border-surface-primary-active" />
```

Agents generate names that match standard Tailwind conventions but are not in the Unity token set; the class is absent from the compiled stylesheet, the element silently renders with no style.

Source: maintainer interview; themes/dist/css/unity.css (@theme block enumerates valid tokens)

### MEDIUM Reach for cn() to compose variant classes when uyTv fits

Wrong:

```tsx
import { cn } from '@payfit/unity-themes'

function Pill({
  size,
  color,
}: {
  size: 'sm' | 'lg'
  color: 'primary' | 'danger'
}) {
  return (
    <span
      className={cn(
        'uy:inline-flex uy:items-center',
        size === 'sm' && 'uy:px-100 uy:text-xs',
        size === 'lg' && 'uy:px-200 uy:text-sm',
        color === 'primary' && 'uy:bg-surface-primary-default',
        color === 'danger' && 'uy:bg-surface-danger-default',
      )}
    />
  )
}
```

Correct:

```tsx
import type { VariantProps } from '@payfit/unity-themes'

import { uyTv } from '@payfit/unity-themes'

const pill = uyTv({
  base: 'uy:inline-flex uy:items-center',
  variants: {
    size: { sm: 'uy:px-100 uy:text-xs', lg: 'uy:px-200 uy:text-sm' },
    color: {
      primary: 'uy:bg-surface-primary-default',
      danger: 'uy:bg-surface-danger-default',
    },
  },
})
type PillProps = VariantProps<typeof pill>
function Pill(props: PillProps) {
  return <span className={pill(props)} />
}
```

cn() / classNames / clsx are for ad-hoc conditional class strings; component-scoped variant APIs with multiple axes (size × color × intent) belong in `uyTv`, which gives a typed `VariantProps` signature and pre-applied conflict resolution.

Source: maintainer interview; themes/src/utils/tailwind-variants.ts

## See also

- `unity-find-component` — the decision tree's "React Aria + uy: classes" branch
  when no Unity component fits.
- `unity-contribute-component` — `uyTv` is the contributor's variant tool.
- `unity-themes-tokens-and-docs` — token discipline if you need a new token
  rather than reusing an existing one.
