Layout
Masonry
A masonry layout — Pinterest's grid: items of different heights packed into columns with no gaps under the short ones.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
columns | number | — | Most columns to lay out. Default `3`. |
columnWidth | number | — | Narrowest a column may get, in px; fewer columns are used below it. Default `240`. |
gap | MasonryGap | — | Space between items, in spacing steps. Default `4`. |
balance | boolean | — | 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. |
children | ReactNode | — | — |
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
Masonry declares 'use client' because it needs state, effects or event handlers. Importing it into a Server Component creates a client boundary at this component — everything above it stays on the server.