Responsive layouts and SSR
Choose rows, columns, masonry, grid, bento, mosaic, or accordion, set row height, columns, and spacing per container width with responsive(), crop photos into tiles with a featured photo or a +N count, and prevent layout shift during SSR and hydration with defaultContainerWidth and breakpoints.
PhotoAlbum has seven layouts. Its numbers, such as the column count, can
change with the width of the album's container, and the server can render the
same layout that the browser will show.
Find the right album layout
Resize the container and compare the seven layouts.
Choose a layout
| Layout | What it does | Use it when |
|---|---|---|
rows | Photos share a row height and each row fills the width. Default. | Photos have mixed shapes. |
columns | Photos fill a fixed number of columns in reading order. Column widths vary so all columns end at the same height. | The column count and order matter. |
masonry | Equal-width columns. Each photo goes into the shortest column. | Density matters more than strict order. |
grid | Same-size tiles in equal columns. Photos are cropped. | Many photos must fit on one screen. |
bento | Big and small tiles on a grid without gaps. Photos are cropped. | A portfolio or project page. |
mosaic | A few photos fill one frame; the last tile shows how many more there are. | A card, a post, or a header. |
accordion | Slices share one strip; the slice under the pointer or focus opens. | A hero area with 3 to 8 photos. |
<PhotoAlbum :photos="photos" :layout="{ type: 'rows', targetRowHeight: 240 }" />
<PhotoAlbum :photos="photos" :layout="{ type: 'columns', columns: 3 }" />
<PhotoAlbum :photos="photos" :layout="{ type: 'masonry', columns: 4 }" />targetRowHeight defaults to 300 pixels: a larger value puts fewer photos in
a row. columns defaults to 3. spacing is the gap between photos (default
8), and padding is space inside each photo item (default 0).
Crop photos into tiles
grid, bento, mosaic, and accordion crop each photo to fill its tile, as
CSS object-fit: cover does. CSS positions every tile, so the server HTML
already has the final layout at every container width. These layouts do not
need defaultContainerWidth.
<template>
<PhotoAlbum
:photos="photos"
:layout="{ type: 'grid', columns: responsive({ 0: 2, 640: 4 }), aspectRatio: 1 }"
/>
<PhotoAlbum
:photos="photos"
:layout="{
type: 'bento',
columns: responsive({ 0: 2, 768: 4 }),
featured: (photo) => photo.meta?.cover === true,
}"
/>
<PhotoAlbum :photos="photos" :layout="{ type: 'mosaic', aspectRatio: 16 / 9, max: 5 }" />
<PhotoAlbum :photos="photos" :layout="{ type: 'accordion', aspectRatio: 2 }" />
</template>| Layout | Option | Default | Meaning |
|---|---|---|---|
grid | columns | 3 | Columns. Accepts responsive(). |
grid | aspectRatio | 1 | Width divided by height of every tile. |
bento | columns | 4 | Columns. Accepts responsive(). |
bento | aspectRatio | 4 / 3 | Width divided by height of one grid cell. |
bento | featured | the first photo | (photo, index) => boolean: photos that get a big tile. |
mosaic | aspectRatio | 4 / 3 | Width divided by height of the whole frame. |
mosaic | max | 5 | Photos shown in the frame. |
accordion | aspectRatio | 2 | Width divided by height of the whole strip. |
These rules can break the result:
- Bento and mosaic may move a photo. Bento moves a photo at most four places
to crop less and to leave no empty cells. Mosaic arranges its photos inside
the frame. The page, the Tab key, and the lightbox all follow the order on
screen.
open(index)and theindexinfeaturedstill use the position inphotos. A parentPhotoGroupkeeps its own navigation order. - Bento chooses its order at the largest column count. With
responsive()columns, smaller counts keep that order and change only the tile shapes, so the order never changes with the width. - Mosaic shows the first
maxphotos. The last tile shows+N. The lightbox still contains every photo and announces its position, such as "Slide 6 of 8". - The accordion needs a pointer or a keyboard to open slices. The first slice is open until another slice has the pointer or keyboard focus. On a touch screen, a tap opens the lightbox.
- Responsive values must be
responsive()or have breakpoints. CSS container queries applycolumns,spacing, andpadding. A plain function needs thebreakpointsprop to know where to sample it; without it, the album uses the function's value at width0.
Change values with the container width
responsive() takes a value per minimum container width in pixels. Nuxt
auto-imports it.
<template>
<PhotoAlbum
:photos="photos"
:layout="{
type: 'columns',
columns: responsive({ 0: 2, 640: 3, 1024: 4 }),
}"
:spacing="responsive({ 0: 4, 640: 8, 1024: 12 })"
/>
</template>The largest matching width wins: a 700-pixel album has three columns. The width is the album's container, not the browser window, so the same album works in a full page, a card, or a sidebar.
responsive() works for spacing, padding, targetRowHeight, and
columns. It cannot switch the layout type. For a smooth formula, pass a
function of the container width instead:
<PhotoAlbum :photos="photos" :spacing="(width) => Math.min(16, Math.max(4, width / 80))" />Start with 4 pixels of spacing on narrow containers and 8 to 12 on wide ones. Use fewer columns when most photos are portraits or captions need room.
Prevent a layout jump after server rendering
grid, bento, mosaic, and accordion never jump: CSS places their tiles
before any script runs. The other layouts compute positions from the width.
The server does not know how wide the album will be. Give it the width the
album usually has, and the first HTML already has the right rows or columns:
<PhotoAlbum
:photos="photos"
:default-container-width="1024"
:layout="{ type: 'columns', columns: responsive({ 0: 2, 640: 3, 1024: 4 }) }"
/>After the page loads, the album measures its container and uses the real
width. Choose a value that is one of your responsive() keys and close to the
common width, such as the width of your content column on desktop. Columns and
masonry near the top of the page benefit most.
When you use a function instead of responsive(), also list the widths the
measurement should snap to:
<PhotoAlbum
:photos="photos"
:spacing="(width) => (width < 640 ? 4 : 12)"
:default-container-width="1024"
:breakpoints="[375, 640, 1024, 1280]"
/>responsive() adds its own keys to the breakpoints for you.
width and height of every photo. Wrong values
still render, but photos then change their shape when they load.Check the result
Open the page at a narrow, a medium, and a wide window, with the album inside
its real parent. The column count and spacing change at your breakpoints. Reload
with a slow network: the album keeps its shape while images load, and does not
jump after hydration at the width you chose for defaultContainerWidth. For
grid, bento, mosaic, and accordion, turn off JavaScript in the browser
and reload: the tiles have the same size and place as with JavaScript.








