Layout

Masonry

A masonry layout — Pinterest's grid: items of different heights packed into columns with no gaps under the short ones.

Client componentSource

Import

import { Masonry } from '@the_viveksingh/vivek-ui'

Cards of different heights

A ResizeObserver on the container decides how many columns fit (columnWidth, capped at columns); one shared observer reports each item's height so the next item goes into the shortest column. Before measurement - and on the server - items are dealt round-robin, so the first paint is already a grid.

Zero dependencies

Nothing to audit but React.

Logical properties only

No left or right anywhere.

RTL is free.

A test enforces it.

Keyboard maps from the APG

Documented per component.

Tested per key.

Static CSS

One stylesheet, bundled at build time.

No runtime, no style tags, no flash.

Cacheable like any other asset.

Works before hydration.

Tree-shakes

Import a Button, ship a Button.

Tokens are the API

--vk-* is public.

Renaming one is a major bump.

Theme by overriding them.

Every selector in :where()

Zero specificity.

Your CSS always wins.

Server-safe

No window at module scope.

use client only where state lives.

Every client component is hydrated in the suite.

Portals wait for the DOM.

Measurement happens in effects.

<Masonry columns={3} columnWidth={220} gap={4}>
  {posts.map((post) => (
    <Card key={post.id}>
      <Image src={post.cover} alt={post.coverAlt} ratio={post.ratio} />
      <Heading level={3} size="sm">{post.title}</Heading>
      <Text size="sm" tone="muted">{post.excerpt}</Text>
    </Card>
  ))}
</Masonry>

Strict reading order

DOM order follows columns, not visual rows, so keep Masonry for content where order is not the point - photos, cards, quotes - and reach for Grid when it is. balance={false} deals items round-robin, which keeps left-to-right order at the cost of ragged column ends.

Zero dependencies

Nothing to audit but React.

Logical properties only

No left or right anywhere.

RTL is free.

A test enforces it.

Keyboard maps from the APG

Documented per component.

Tested per key.

Static CSS

One stylesheet, bundled at build time.

No runtime, no style tags, no flash.

Cacheable like any other asset.

Works before hydration.

Tree-shakes

Import a Button, ship a Button.

Tokens are the API

--vk-* is public.

Renaming one is a major bump.

Theme by overriding them.

Every selector in :where()

Zero specificity.

Your CSS always wins.

Server-safe

No window at module scope.

use client only where state lives.

Every client component is hydrated in the suite.

Portals wait for the DOM.

Measurement happens in effects.

<Masonry columns={4} columnWidth={160} gap={2} balance={false}>
  {photos.map((photo) => (
    <Image key={photo.src} src={photo.src} alt={photo.alt} rounded="md" />
  ))}
</Masonry>

Props

Generated from the package's own type declarations, so this table cannot drift from the code.

Props for Masonry
PropTypeDefaultDescription
columnsnumber—Most columns to lay out. Default `3`.
columnWidthnumber—Narrowest a column may get, in px; fewer columns are used below it. Default `240`.
gapMasonryGap—Space between items, in spacing steps. Default `4`.
balanceboolean—Place each item in the currently shortest column, using measured heights. Default `true`. `false` deals items round-robin, which keeps reading order strictly left-to-right at the cost of ragged column ends.
childrenReactNode——

Every remaining prop is spread onto the root element, so all standard HTML and ARIA attributes work. className and style are merged with the library's own, never replaced, and the ref forwards to the root DOM node.

Rendering