Aspect Ratio

Collage Grid

A collage of frames where the grid holds the ratio rather than each tile. The lead frame spans two columns and the tail folds into a counter.

  • aspect ratio
  • collage
  • gallery
  • grid

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-006?lang=en

Главная
Услуги
О студии
Контакты
Блог+1
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-006" (Collage Grid) 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-006.json

Registry item: https://vibeui.ru/r/aspect-006.json
Installs to: components/vibeui/aspect-006.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 collage of frames where the grid holds the ratio rather than each tile. The lead frame spans two columns and the tail folds into a counter.

A collage of frames: one lead tile and smaller ones, the whole composition holding 3:2 and reflowing in a narrow column. The tail of the list folds into a counter on the last tile. Zero dependencies, one file, its own palette, no client JS.

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

<Aspect006
  tiles={[{ label: "Home", hue: 250 }, { label: "Services", hue: 30 }]}
  visible={5}
/>

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-006-* palette — do not swap it for your theme tokens
- the ratio on the grid rather than the tiles: otherwise the collage falls apart at a different count
- the grid on an inner element: a container query does not apply to the container itself
- the lead tile spanning two columns and two rows — without hierarchy a collage reads as mosaic
- the counter on the last tile: a gallery must not grow without end
- the reflow to two columns at narrow widths

## 6. You may change
- the tiles array: label and hue per tile
- visible — how many frames show before folding
- moreLabel — the glyph before the hidden count
- the photos: put an <img> inside the tiles

## 7. Rules
- Fewer than three frames break the composition: the lead tile takes everything and no small ones remain.
- The hue is a placeholder for when a photo is missing. With real images it is invisible.
- The component opens no lightbox: clicking a tile is the caller's job.
- 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-006.json
https://vibeui.ru/r/aspect-006.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-aspect-006-*. Сетка и её соотношение 3:2 живут на внутреннем элементе, потому что корень объявлен контейнером, а контейнерный запрос действует только на потомков. Первая плитка занимает две колонки и две строки; в узкой колонке сетка перестраивается в две колонки и три строки с пропорцией 3:4. Оттенок плитки задаётся переменной.

Component source

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

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Aspect006Tile = {
  label: string
  hue?: number
}

export type Aspect006Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  tiles?: Aspect006Tile[]
  /** Сколько кадров показывать сверх главного: остальные сворачиваются в счётчик. */
  visible?: number
  moreLabel?: string
}

// Идея компонента: коллаж из кадров разного размера. Соотношение задаётся не
// каждой плитке, а всей сетке: главный кадр занимает две колонки и две строки,
// мелкие — по одной, и вся композиция держит 3:2. Хвост сворачивается в
// счётчик на последней плитке — коллаж не должен расти бесконечно.
const STYLES = `
:where([data-vibeui-block="aspect-006"]){
--vibeui-aspect-006-border:oklch(0.91 0.006 265);
--vibeui-aspect-006-fg:oklch(0.99 0.003 265);
--vibeui-aspect-006-radius:0.875rem;
--vibeui-aspect-006-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
container-type:inline-size;
}
[data-vibeui-block="aspect-006"]{display:block;width:100%;box-sizing:border-box;font-family:var(--vibeui-aspect-006-font)}
/* Соотношение держит сетка целиком, а не каждая плитка отдельно. */
/* Раскладка живёт на внутренней рамке, а не на корне: контейнерный запрос
   применяется к потомкам контейнера, но не к нему самому. */
[data-vibeui-block="aspect-006"] [data-part="grid"]{
display:grid;gap:0.5rem;
grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);
width:100%;box-sizing:border-box;aspect-ratio:3 / 2;
}
[data-vibeui-block="aspect-006"] [data-part="tile"]{
position:relative;overflow:hidden;border-radius:var(--vibeui-aspect-006-radius);
border:1px solid var(--vibeui-aspect-006-border);
background:
radial-gradient(90% 80% at 25% 20%,oklch(0.88 0.07 var(--vibeui-aspect-006-hue,250)),transparent 70%),
linear-gradient(150deg,oklch(0.78 0.09 var(--vibeui-aspect-006-hue,250)),oklch(0.52 0.11 var(--vibeui-aspect-006-hue,250)));
}
[data-vibeui-block="aspect-006"] [data-part="tile"]:first-child{grid-column:span 2;grid-row:span 2}
[data-vibeui-block="aspect-006"] img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
[data-vibeui-block="aspect-006"] [data-part="label"]{
position:absolute;left:0.5rem;bottom:0.5rem;
padding:0.125rem 0.4375rem;border-radius:0.3125rem;
background:oklch(0.18 0.02 265 / 55%);color:var(--vibeui-aspect-006-fg);
font-size:0.6875rem;font-weight:600;
}
/* Счётчик на последней плитке: коллаж не растёт бесконечно. */
[data-vibeui-block="aspect-006"] [data-part="more"]{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
background:oklch(0.18 0.02 265 / 55%);color:var(--vibeui-aspect-006-fg);
font-size:1rem;font-weight:650;font-variant-numeric:tabular-nums;
}
/* В узкой колонке коллаж превращается в две колонки и три строки. */
@container (max-width: 26rem){
[data-vibeui-block="aspect-006"] [data-part="grid"]{grid-template-columns:repeat(2,1fr);grid-template-rows:repeat(3,1fr);aspect-ratio:3 / 4}
[data-vibeui-block="aspect-006"] [data-part="tile"]:first-child{grid-column:span 2;grid-row:span 2}
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="aspect-006"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_TILES: Aspect006Tile[] = [
  { label: "Главная", hue: 250 },
  { label: "Услуги", hue: 30 },
  { label: "О студии", hue: 150 },
  { label: "Контакты", hue: 300 },
  { label: "Блог", hue: 90 },
  { label: "Портфолио", hue: 200 },
]

/**
 * Коллаж кадров: соотношение держит сетка, хвост сворачивается в счётчик.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Aspect006({
  tiles = DEFAULT_TILES,
  visible = 5,
  moreLabel = "+",
  className,
  style,
  ...props
}: Aspect006Props) {
  const shown = tiles.slice(0, Math.max(3, visible))
  const rest = tiles.length - shown.length

  return (
    <>
      <style href="vibeui-aspect-006" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="aspect-006"
        className={className}
        style={style as CSSProperties}
      >
        <div data-part="grid">
          {shown.map((tile, index) => {
            const last = index === shown.length - 1

            return (
              <div
                key={tile.label}
                data-part="tile"
                style={
                  {
                    "--vibeui-aspect-006-hue": tile.hue ?? 250,
                  } as CSSProperties
                }
              >
                <span data-part="label">{tile.label}</span>
                {last && rest > 0 ? (
                  <span data-part="more">
                    {moreLabel}
                    {rest}
                  </span>
                ) : null}
              </div>
            )
          })}
        </div>
      </div>
    </>
  )
}