# keyframes

Emit an `@keyframes` rule and return its generated animation name.

`keyframes` emits ordered frame stops into the shared stylesheet. The compiler replaces the call with a name derived from its binding, so styles reference the animation without a handwritten name. The [Keyframes](/docs/guides/keyframes) guide covers reduced motion and timeline ranges.

```ts title="Notice.tsx"
import { style } from 'zyzz'
import { keyframes } from 'zyzz/web'

// Emits `@keyframes z-k-enter{from{opacity:0;}to{opacity:1;}}`
const enter = keyframes({ from: { opacity: 0 }, to: { opacity: 1 } })

export namespace styles {
  export const notice = style({
    // The reference compiles to `animation-name:z-k-enter`
    animationName: enter,
    animationDuration: '200ms',
  })
}
```

## Signature

```ts
// Frame stops, optionally inside grouping keys
keyframes(frames, context?)
```

## Parameters

### frames

* **Type:** `{ [stop: string]: Style.LiteralDeclarations }`

Frame stops mapped to declarations, in authored order. Stops are `from`, `to`, percentages, timeline ranges such as `entry 0%`, or comma-separated lists of these. Frames cannot contain nested rules or importance.

```ts
import { keyframes } from 'zyzz/web'

export const pulse = keyframes({
  // One frame for both stops
  '0%, 100%': { opacity: 1 },
  '50%': { opacity: 0.5 },
})
```

Timeline ranges are `contain`, `cover`, `entry`, `entry-crossing`, `exit`, and `exit-crossing`, each followed by a percentage.

```ts
import { keyframes } from 'zyzz/web'

export const reveal = keyframes({
  // Stops within a view timeline's entry range
  'entry 0%': { opacity: 0 },
  'entry 100%': { opacity: 1 },
})
```

### frames\[atRule]

* **Type:** `` '@layer' | `@${'container' | 'layer' | 'media' | 'supports'}${' ' | '\t' | '\n' | '\r' | '\f' | '(' | `/*${string}*/`}${string}` ``

Grouping keys around a complete set of frames. Each value holds a complete definition or further grouping keys, and outer keys emit outer groups. A bare `@layer` key emits an anonymous layer.

```ts
import { keyframes } from 'zyzz/web'

export const fade = keyframes({
  // Emits the rule inside `@layer animations`
  '@layer animations': { from: { opacity: 0 }, to: { opacity: 1 } },
})
```

### context.id

* **Type:** `string`
* **Default:** `undefined`

A fixed identity, which replaces the name derived from the binding. It also separates two modules that declare the same binding name. Without a compiler transform, such as in tests, a call requires it.

```ts
// Returns `z-kid-fade` with or without a compiler transform
keyframes({ from: { opacity: 0 } }, { id: 'fade' })
```

## Returns

### name

* **Type:** `string`

The generated animation name, such as `z-k-enter`. Imports, re-exports, and packed libraries keep the same name. The compiler omits definitions that are neither exported nor referenced.

```ts
// Compiles to `animation-name:z-k-enter`
style({ animationName: enter })
```

## Errors

TypeScript rejects invalid stops and declarations.

```ts
import { keyframes } from 'zyzz/web'

// `middle` is not a frame stop
keyframes({ middle: { opacity: 0.5 } })
// error: Type '{ opacity: number; }' is not assignable to type 'never'.
```

The compiler reports `Source.ExtractError` for nested rules or importance inside a frame. Without a compiler transform, a call without `context.id` throws an `Error`. Native builds reject the call with `Native.CompileError`.
