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
Use it with AI
- 1. Copy the link.
- 2. Write to your agent in your own words and drop the link into the sentence.
- 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
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.jsonhttps://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>
</>
)
}