Motiva
Мои коллекции
← каталог

Masonry Columns

Пинтерест-стена на чистом CSS: одно свойство columns, ни строчки JS и ни одного медиазапроса. Честно описано ограничение — порядок чтения идёт по колонкам.

csshoverrevealbasic~2 КБreduced-motion ✓
↗
↕ демо длиннее кадра — прокрутите его

Пропсы

Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.

ПропТипПо умолчаниюЧто это
items?MasonryColumnsItem[]ITEMS
min?number210Минимальная ширина колонки, px.
gap?number12Зазор, px.
MasonryColumnsItem — что внутри
  • src: string
  • title?: string
  • tag?: string

Установка

npx shadcn@latest add https://motiva.pages.dev/r/masonry-columns.json
Что нужно в проекте до установки
  1. Файл components.json в корне

    Из него CLI узнаёт, куда класть файлы. Алиасы — в такой форме:

    "aliases": { "components": "@/components", "lib": "@/lib" }

    Нет файла — установка не начнётся вовсе.

  2. Алиас @ объявлен дважды

    В корневом tsconfig.json и в конфиге сборщика. В Vite корневой tsconfig часто содержит только references — этого мало.

    "paths": { "@/*": ["./src/*"] }        // tsconfig.json
    resolve.alias = { "@": "/src" }        // vite.config.ts

    Без paths CLI молча создаёт папку с именем @.

  3. Токены подключены один раз на приложение

    Они приезжают вместе с записью — достаточно импортировать их в точке входа:

    import "@/styles/motiva-tokens.css";

    Без них блок потеряет цвета, ритм и тему.

  4. Своя типографика не спорит с блоком

    Наследуемые свойства протекают внутрь: text-align: center на обёртке отцентрует содержимое секции. То же касается font-familyи line-height.

  5. Проект без TypeScript? всё равно нужен tsconfig

    CLI грузит tsconfig.json безусловно, а при "tsx": false идёт за jsconfig.json и падает так же. Минимум — файл с pathsи "tsx": true в components.json.

    Приехавший рядом .tsx можно не трогать — вам нужен .vanilla.js.

Экспорт

↓ Скачать .zip

Код

/* вся раскладка — эта одна строка */
.msc { columns: var(--msc-min, 210px); column-gap: var(--msc-gap, 12px); width: 100%; }

.msc-item { break-inside: avoid; margin: 0 0 var(--msc-gap, 12px); position: relative;
  border-radius: var(--ds-radius, 14px); overflow: hidden;
  background: var(--ds-surface, oklch(0.18 0.02 275)); }
.msc-item img { display: block; width: 100%; height: auto;
  transition: transform var(--ds-slow, 0.6s) var(--ds-ease-out, cubic-bezier(0.16, 1, 0.3, 1)); }
/* приближается картинка внутри неподвижной рамки — колонки не пересчитываются */
.msc-item:hover img { transform: scale(1.06); }

.msc-meta { position: absolute; inset: auto 0 0 0; padding: 30px 12px 10px;
  color: oklch(0.98 0 0);
  background: linear-gradient(to top, oklch(0 0 0 / 0.76), oklch(0 0 0 / 0));
  opacity: 0; translate: 0 20%;
  transition: opacity var(--ds-base, 0.32s),
              translate var(--ds-base, 0.32s) var(--ds-ease-out, cubic-bezier(0.16, 1, 0.3, 1)); }
.msc-item:hover .msc-meta, .msc-item:focus-within .msc-meta { opacity: 1; translate: 0 0; }
.msc-t { display: block; font-weight: 700; font-size: var(--ds-text-sm, 0.9rem);
  letter-spacing: var(--ds-tracking-tight, -0.03em);
  font-family: var(--ds-font, system-ui, sans-serif); }
.msc-g { font-family: var(--ds-font-mono, ui-monospace, monospace);
  font-size: var(--ds-text-xs, 0.72rem); opacity: 0.8; }

@media (prefers-reduced-motion: reduce) {
  .msc-item img, .msc-meta { transition: none; }
  .msc-meta { opacity: 1; translate: 0 0; }
}
лицензия: MITавтор: Motivaисточник: оригинал (Motiva)v1.0.0