# Aspect Ratio

## Usage

`AspectRatio` keeps a fixed width-to-height ratio as its container changes size. Use it for images, video, thumbnails, and other media that should stay in proportion. The width comes from the parent, and the height is derived from `ratio`, so an ancestor needs a definite width.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
</script>

<template>
  <AspectRatio :ratio="16 / 9" fit="cover">
    <img alt="Landscape" src="/photo.jpg">
  </AspectRatio>
</template>
```

`ratio` is width divided by height. Write `16 / 9` rather than a rounded decimal. `shape` is `rectangle` or `ellipse`; an ellipse with `ratio` `1` is a circle. `fit` sizes the child: `cover` fills and crops, `contain` letterboxes, and `center` keeps the natural size. Leave `fit` unset when the child sizes itself.

The ratio is written to the CSS variable `--astryx-aspect-ratio`, not as an inline `aspect-ratio` declaration, so a stylesheet can override it.

### Anatomy

- **Ratio box**: The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.
- **Content slot**: A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Pass one child when `fit` is set. Every direct child is stretched, so put an overlay inside that child. |
| Do | Give images an `alt`, or `alt=""` when the image is decorative. The ratio box adds no accessible name of its own. |
| Don't | Use it as a general layout container. Use `Section`, `Card`, or `Layout` for page regions. |
| Don't | Set only a height. The width comes from the container, so a lone height clamps the box off ratio. |

## Examples

### Circle

shape ellipse with ratio 1 clips the image to a circle.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
import { Center } from '@astryx-vue/core/Center'
</script>

<template>
  <Center :width="300">
    <AspectRatio :ratio="1" fit="cover" shape="ellipse">
      <img alt="Circular image" src="/template-assets/light-home-square-1.png">
    </AspectRatio>
  </Center>
</template>
```

### Image gallery

A grid of images that share a 4:3 frame.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
import { Center } from '@astryx-vue/core/Center'
import { Grid } from '@astryx-vue/core/Grid'

const images = [
  { id: 1, alt: 'Mountain landscape' },
  { id: 2, alt: 'Ocean sunset' },
  { id: 3, alt: 'Forest trail' },
  { id: 4, alt: 'City skyline' },
  { id: 5, alt: 'Desert dunes' },
  { id: 6, alt: 'Snowy peaks' },
]
</script>

<template>
  <Center :width="600">
    <Grid :columns="3" :gap="4" width="100%">
      <AspectRatio v-for="image in images" :key="image.id" :ratio="4 / 3" fit="cover">
        <img :alt="image.alt" src="/template-assets/illustrative-horizontal-1.png" style="border-radius: var(--astryx-radius-element)">
      </AspectRatio>
    </Grid>
  </Center>
</template>
```

### Square

A 1:1 frame for an avatar or a square image.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
import { Center } from '@astryx-vue/core/Center'
</script>

<template>
  <Center :width="300">
    <AspectRatio :ratio="1" fit="cover">
      <img alt="1:1 square" src="/template-assets/light-home-square-1.png">
    </AspectRatio>
  </Center>
</template>
```

### Widescreen

A 16:9 frame around an image.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
import { Center } from '@astryx-vue/core/Center'
</script>

<template>
  <Center :width="600">
    <AspectRatio :ratio="16 / 9" fit="cover">
      <img alt="16:9 widescreen" src="/template-assets/light-scene-horizontal-1.png">
    </AspectRatio>
  </Center>
</template>
```

### Loading skeleton

The frame holds a skeleton while the image is still loading.

```vue
<script setup lang="ts">
import { AspectRatio } from '@astryx-vue/core/AspectRatio'
import { Center } from '@astryx-vue/core/Center'
import { Skeleton } from '@astryx-vue/core/Skeleton'
</script>

<template>
  <Center :width="600">
    <AspectRatio :ratio="16 / 9">
      <Skeleton height="100%" width="100%" />
    </AspectRatio>
  </Center>
</template>
```

## Aspect Ratio Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `ratio` | `number` | - | Width divided by height. `16 / 9` is a widescreen frame. Written to `--astryx-aspect-ratio`, not as an inline `aspect-ratio`. |
| `shape` | `'rectangle' \| 'ellipse'` | `'rectangle'` | Container shape. Both shapes follow `ratio`. `ellipse` with `ratio` `1` is a circle. |
| `fit` | `'cover' \| 'contain' \| 'center'` | - | How the child fills the box. `cover` crops, `contain` letterboxes, `center` keeps the natural size. Omit it and the child sizes itself. |

## Slots

| Name | Description |
| --- | --- |
| `default` | The content inside the ratio box. Pass one child when `fit` is set. |

## CSS variables

| Name | Description |
| --- | --- |
| `--astryx-aspect-ratio` | The ratio. A stylesheet can override the variable or `aspect-ratio` itself. |

## Native attributes

`AspectRatio` renders a `<div>`.

The following go to that element:

- HTML attributes.
- `aria-*` attributes.
- `data-*` attributes.
- `class`.
- `style`.

`class` is merged. The root also sets `data-shape`.
