Aspect Ratio

Ratio Frame

A frame with a fixed ratio that holds the space until the content loads. The ratio arrives as a variable rather than a class — "16 / 9" is data.

  • aspect ratio
  • media
  • placeholder
  • layout

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/aspect-001?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "aspect-001" (Ratio Frame) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/aspect-001.json

Registry item: https://vibeui.ru/r/aspect-001.json
Installs to: components/vibeui/aspect-001.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A frame with a fixed ratio that holds the space until the content loads. The ratio arrives as a variable rather than a class — "16 / 9" is data.

A frame with a fixed aspect ratio: the content fills it completely, and an empty one shows hatching with a label. The ratio is a string prop, so any works — 16/9, 4/3, 1/1, 21/9. Zero dependencies, one file, its own palette, no client JS.

## 3. How to use it
import { Aspect001 } from "@/components/vibeui/aspect-001"

<Aspect001 ratio="16 / 9">
  <img src="/cover.jpg" alt="Article cover" />
</Aspect001>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-aspect-001-* palette — do not swap it for your theme tokens (bg-muted, border-border and the like)
- the ratio as a variable rather than a set of classes: a proportion is data
- object-fit:cover on the direct children: without it the image stretches and the ratio loses its point
- overflow:hidden together with the radius — otherwise the image escapes the corners
- the hatching and label on an empty frame: a plain grey box reads as broken
- aria-hidden on the placeholder label: it is decorative

## 6. You may change
- ratio — any proportion as a string: "16 / 9", "4 / 3", "1 / 1"
- children — an img, video, iframe or your own markup
- label — the empty-frame caption
- width and outer spacing through className

## 7. Rules
- The component loads no images and tracks no loading: it only holds the space.
- An <img> inside needs an alt — the frame does not supply one.
- Do not set a height through className: it is derived from the width and the ratio.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/aspect-001.json
https://vibeui.ru/r/aspect-001.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-aspect-001-*. Соотношение приходит пропом в переменную --vibeui-aspect-001-ratio и подставляется в aspect-ratio: плодить класс на каждую пропорцию значит заранее решать за пользователя, какие ему понадобятся. Прямые потомки img, video и iframe растягиваются на всю рамку с object-fit:cover. Пустая рамка показывает штриховку и подпись, чтобы не выглядеть поломкой.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties, ReactNode } from "react"

export type Aspect001Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  /** Соотношение сторон: «16 / 9», «4 / 3», «1 / 1». */
  ratio?: string
  /** Подпись поверх подложки, пока содержимого нет. */
  label?: string
  children?: ReactNode
}

// Идея компонента: рамка с заданным соотношением сторон, которая держит место
// до загрузки содержимого. Соотношение приходит переменной, а не классом:
// «16 / 9» и «4 / 3» — это данные, а плодить класс на каждую пропорцию значит
// заранее решать за пользователя, какие ему понадобятся.
const STYLES = `
:where([data-vibeui-block="aspect-001"]){
--vibeui-aspect-001-ratio:16 / 9;
--vibeui-aspect-001-bg:oklch(0.955 0.005 265);
--vibeui-aspect-001-fg:oklch(0.5 0.014 265);
--vibeui-aspect-001-border:oklch(0.9 0.006 265);
--vibeui-aspect-001-radius:0.75rem;
--vibeui-aspect-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="aspect-001"]{
position:relative;display:block;width:100%;box-sizing:border-box;overflow:hidden;
aspect-ratio:var(--vibeui-aspect-001-ratio);
border:1px solid var(--vibeui-aspect-001-border);
border-radius:var(--vibeui-aspect-001-radius);
background:var(--vibeui-aspect-001-bg);
font-family:var(--vibeui-aspect-001-font);
}
/* Содержимое обязано заполнять рамку целиком, иначе соотношение бессмысленно. */
[data-vibeui-block="aspect-001"] > img,
[data-vibeui-block="aspect-001"] > video,
[data-vibeui-block="aspect-001"] > iframe{
display:block;width:100%;height:100%;border:0;object-fit:cover;
}
[data-vibeui-block="aspect-001"] [data-part="label"]{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
gap:0.5rem;color:var(--vibeui-aspect-001-fg);font-size:0.8125rem;
}
/* Диагональная штриховка: пустая рамка не должна выглядеть сломанной. */
[data-vibeui-block="aspect-001"] [data-part="label"]::before{
content:"";position:absolute;inset:0;pointer-events:none;
background-image:repeating-linear-gradient(135deg,var(--vibeui-aspect-001-border) 0 1px,transparent 1px 10px);
opacity:.5;
}
[data-vibeui-block="aspect-001"] [data-part="label"] span{position:relative;font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="aspect-001"] *{animation:none!important;transition:none!important}}
`

/**
 * Рамка заданного соотношения: держит место до загрузки содержимого.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Aspect001({
  ratio = "16 / 9",
  label = "16 / 9",
  children,
  className,
  style,
  ...props
}: Aspect001Props) {
  const palette = {
    "--vibeui-aspect-001-ratio": ratio,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-aspect-001" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="aspect-001"
        className={className}
        style={palette}
      >
        {children ?? (
          <span data-part="label" aria-hidden="true">
            <span>{label}</span>
          </span>
        )}
      </div>
    </>
  )
}