Collage, similarly to Masonry, creates a deterministic grid layout that can absolutely position and virtualize images.

also known as Photo Composition

Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
columns
Required
2 | 3 | 4
-

Number of columns (2 - 4). Note that Collage assumes at least 2 * columns images will be provided. If fewer images are provided, care will be needed to avoid TypeErrors. See Columns example for more details.

height
Required
number
-

Height of the collage.

renderImage
Required
(arg1: { width: number; height: number; index: number }) => React.Node
-

Callback to render the collage images.

width
Required
number
-

Width of the collage.

cover
boolean
-

Whether or not the first image is a cover image. See Cover Image example for more details.

gutter
number
-

The amount of vertical and horizontal space between images. See Gutter example for more details.

layoutKey
number
-

Depending on the number of columns of the collage, there may be multiple layouts available. If there are N layouts available, (layoutKey % N) will determine which layout is used. See Layout Key example for more details.

Accessibility

Variants

Columns

function _optionalChain(ops) {
  let lastAccessLHS = undefined;
  let value = ops[0];
  let i = 1;
  while (i < ops.length) {
    const op = ops[i];
    const fn = ops[i + 1];
    i += 2;
    if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
      return undefined;
    }
    if (op === 'access' || op === 'optionalAccess') {
      lastAccessLHS = value;
      value = fn(value);
    } else if (op === 'call' || op === 'optionalCall') {
      value = fn((...args) => value.call(lastAccessLHS, ...args));
      lastAccessLHS = undefined;
    }
  }
  return value;
}
import { Box, Collage, Flex, Image, Mask, Text } from 'gestalt';

const images = [
  {
    color: 'rgb(111, 91, 77)',
    naturalHeight: 751,
    naturalWidth: 564,
    src: 'https://i.ibb.co/Lx54BCT/stock1.jpg',
  },
  {
    color: 'rgb(231, 186, 176)',
    naturalHeight: 200,
    naturalWidth: 98,
    src: 'https://i.ibb.co/7bQQYkX/stock2.jpg',
  },
  {
    color: '#000',
    naturalHeight: 300,
    naturalWidth: 200,
    src: 'https://i.ibb.co/d0pQsJz/stock3.jpg',
  },
  {
    color: '#000',
    naturalHeight: 517,
    naturalWidth: 564,
    src: 'https://i.ibb.co/SB0pXgS/stock4.jpg',
  },
  {
    color: '#000',
    naturalHeight: 806,
    naturalWidth: 564,
    src: 'https://i.ibb.co/jVR29XV/stock5.jpg',
  },
  {
    color: '#000',
    naturalHeight: 200,
    naturalWidth: 200,
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
];

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Flex gap={12} wrap>
        {[2, 3, 4].map((columns) => (
          <Box key={columns}>
            <Box>
              <Text>columns = {columns}</Text>
            </Box>

            <Collage
              columns={columns}
              height={150}
              renderImage={({ index, width, height }) => {
                const image = images[index];
                return (
                  <Mask height={height} wash width={width}>
                    {_optionalChain([image, 'optionalAccess', (_) => _.src]) ? (
                      <Image
                        alt="collage image"
                        color={image.color}
                        fit="cover"
                        naturalHeight={image.naturalHeight}
                        naturalWidth={image.naturalWidth}
                        src={image.src}
                      />
                    ) : (
                      <Box color="secondary" height={height} width={width} />
                    )}
                  </Mask>
                );
              }}
              width={150}
            />
          </Box>
        ))}
      </Flex>
    </Flex>
  );
}

Gutter

import { Box, Collage, Flex, Image, Mask } from 'gestalt';

const images = [
  {
    color: 'rgb(111, 91, 77)',
    naturalHeight: 751,
    naturalWidth: 564,
    src: 'https://i.ibb.co/Lx54BCT/stock1.jpg',
  },
  {
    color: 'rgb(231, 186, 176)',
    naturalHeight: 200,
    naturalWidth: 98,
    src: 'https://i.ibb.co/7bQQYkX/stock2.jpg',
  },
  {
    color: '#000',
    naturalHeight: 300,
    naturalWidth: 200,
    src: 'https://i.ibb.co/d0pQsJz/stock3.jpg',
  },
  {
    color: '#000',
    naturalHeight: 517,
    naturalWidth: 564,
    src: 'https://i.ibb.co/SB0pXgS/stock4.jpg',
  },
  {
    color: '#000',
    naturalHeight: 806,
    naturalWidth: 564,
    src: 'https://i.ibb.co/jVR29XV/stock5.jpg',
  },
  {
    color: '#000',
    naturalHeight: 200,
    naturalWidth: 200,
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
];

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Box color="secondary" height={300} width={300}>
        <Collage
          columns={3}
          gutter={8}
          height={300}
          renderImage={({ index, width, height }) => {
            const image = images[index];
            return (
              <Mask height={height} wash width={width}>
                {image ? (
                  <Image
                    alt="collage image"
                    color={image.color}
                    fit="cover"
                    naturalHeight={image.naturalHeight}
                    naturalWidth={image.naturalWidth}
                    src={image.src}
                  />
                ) : (
                  <Box color="secondary" height={height} width={width} />
                )}
              </Mask>
            );
          }}
          width={300}
        />
      </Box>
    </Flex>
  );
}

Cover image

import { Box, Collage, Flex, Image, Mask } from 'gestalt';

const coverImage = {
  color: '#000',
  naturalHeight: 806,
  naturalWidth: 564,
  src: 'https://i.ibb.co/jVR29XV/stock5.jpg',
};
const nonCoverImages = [
  {
    color: 'rgb(111, 91, 77)',
    naturalHeight: 751,
    naturalWidth: 564,
    src: 'https://i.ibb.co/Lx54BCT/stock1.jpg',
  },
  {
    color: 'rgb(231, 186, 176)',
    naturalHeight: 200,
    naturalWidth: 98,
    src: 'https://i.ibb.co/7bQQYkX/stock2.jpg',
  },
];

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Box color="secondary" height={300} width={300}>
        <Collage
          columns={3}
          cover
          gutter={8}
          height={300}
          renderImage={({ index, width, height }) => {
            const image = index === 0 ? coverImage : nonCoverImages[index - 1];
            return (
              <Mask height={height} width={width}>
                {image ? (
                  <Image
                    alt="cover image"
                    color={image.color}
                    fit="cover"
                    naturalHeight={image.naturalHeight}
                    naturalWidth={image.naturalWidth}
                    src={image.src}
                  />
                ) : (
                  <Box color="secondary" height={height} width={width} />
                )}
              </Mask>
            );
          }}
          width={300}
        />
      </Box>
    </Flex>
  );
}

Columns with cover image

function _optionalChain(ops) {
  let lastAccessLHS = undefined;
  let value = ops[0];
  let i = 1;
  while (i < ops.length) {
    const op = ops[i];
    const fn = ops[i + 1];
    i += 2;
    if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) {
      return undefined;
    }
    if (op === 'access' || op === 'optionalAccess') {
      lastAccessLHS = value;
      value = fn(value);
    } else if (op === 'call' || op === 'optionalCall') {
      value = fn((...args) => value.call(lastAccessLHS, ...args));
      lastAccessLHS = undefined;
    }
  }
  return value;
}
import { Box, Collage, Flex, Image, Mask, Text } from 'gestalt';

const images = [
  {
    color: 'rgb(111, 91, 77)',
    naturalHeight: 751,
    naturalWidth: 564,
    src: 'https://i.ibb.co/Lx54BCT/stock1.jpg',
  },
  {
    color: 'rgb(231, 186, 176)',
    naturalHeight: 200,
    naturalWidth: 98,
    src: 'https://i.ibb.co/7bQQYkX/stock2.jpg',
  },
  {
    color: '#000',
    naturalHeight: 300,
    naturalWidth: 200,
    src: 'https://i.ibb.co/d0pQsJz/stock3.jpg',
  },
  {
    color: '#000',
    naturalHeight: 517,
    naturalWidth: 564,
    src: 'https://i.ibb.co/SB0pXgS/stock4.jpg',
  },
  {
    color: '#000',
    naturalHeight: 806,
    naturalWidth: 564,
    src: 'https://i.ibb.co/jVR29XV/stock5.jpg',
  },
  {
    color: '#000',
    naturalHeight: 200,
    naturalWidth: 200,
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
];

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Flex wrap>
        {[2, 3, 4].map((columns) => (
          <Box key={columns} padding={2}>
            <Box>
              <Text>columns = {columns}</Text>
            </Box>
            <Collage
              columns={columns}
              cover
              height={150}
              renderImage={({ index, width, height }) => {
                const image = images[index];
                return (
                  <Mask height={height} wash width={width}>
                    {_optionalChain([image, 'optionalAccess', (_) => _.src]) ? (
                      <Image
                        alt="collage image"
                        color={image.color}
                        fit="cover"
                        naturalHeight={image.naturalHeight}
                        naturalWidth={image.naturalWidth}
                        src={image.src}
                      />
                    ) : (
                      <Box color="secondary" height={height} width={width} />
                    )}
                  </Mask>
                );
              }}
              width={150}
            />
          </Box>
        ))}
      </Flex>
    </Flex>
  );
}

Layout key

You can pick a layout using the layout key (layout key is 0 by default).
Depending on the number of columns of the collage, there may be multiple layouts available.
If there are N layouts available, (layoutKey % N) will determine which layout is used.

import { Box, Collage, Flex, Image, Mask, Text } from 'gestalt';

const images = [
  {
    color: 'rgb(111, 91, 77)',
    naturalHeight: 751,
    naturalWidth: 564,
    src: 'https://i.ibb.co/Lx54BCT/stock1.jpg',
  },
  {
    color: 'rgb(231, 186, 176)',
    naturalHeight: 200,
    naturalWidth: 98,
    src: 'https://i.ibb.co/7bQQYkX/stock2.jpg',
  },
  {
    color: '#000',
    naturalHeight: 300,
    naturalWidth: 200,
    src: 'https://i.ibb.co/d0pQsJz/stock3.jpg',
  },
  {
    color: '#000',
    naturalHeight: 517,
    naturalWidth: 564,
    src: 'https://i.ibb.co/SB0pXgS/stock4.jpg',
  },
  {
    color: '#000',
    naturalHeight: 806,
    naturalWidth: 564,
    src: 'https://i.ibb.co/jVR29XV/stock5.jpg',
  },
  {
    color: '#000',
    naturalHeight: 200,
    naturalWidth: 200,
    src: 'https://i.ibb.co/FY2MKr5/stock6.jpg',
  },
];

export default function Example() {
  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <Flex wrap>
        {[0, 1, 2, 3].map((layoutKey) => (
          <Box key={layoutKey} padding={2}>
            <Box>
              <Text>layoutKey = {layoutKey}</Text>
            </Box>

            <Collage
              columns={3}
              height={150}
              layoutKey={layoutKey}
              renderImage={({ index, width, height }) => {
                const image = images[index];
                return (
                  <Mask height={height} wash width={width}>
                    {image ? (
                      <Image
                        alt="collage image"
                        color={image.color}
                        fit="cover"
                        naturalHeight={image.naturalHeight}
                        naturalWidth={image.naturalWidth}
                        src={image.src}
                      />
                    ) : (
                      <Box color="secondary" height={height} width={width} />
                    )}
                  </Mask>
                );
              }}
              width={150}
            />
          </Box>
        ))}
      </Flex>
    </Flex>
  );
}

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Component is not currently available in Figma.
Responsive Web
Ready
Component responds to changing viewport sizes in web and mobile web.