RORK LABJP
PUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicksSIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlightMAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core MLFUNDING — Rork raised $15M to power the next generation of App Store entrepreneursPRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/monthPUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicksSIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlightMAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core MLFUNDING — Rork raised $15M to power the next generation of App Store entrepreneursPRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month
Articles/App Dev
App Dev/2026-07-07Intermediate

Laying Out Variable-Height Images in Two Columns: A Masonry Wallpaper Gallery in a Rork Expo App

From why numColumns cannot pack variable-aspect images cleanly, to a dependency-free column-balancing algorithm, to keeping virtualization with FlashList masonry and a pragmatic no-dependency fallback, building a wallpaper gallery with real code.

Rork492Expo132React Native197FlashList4Image Layout

Premium Article

I was rebuilding the grid screen of my own wallpaper app. Tall photos, wide illustrations, and square minimal designs all share one shelf. With a naive grid built by handing numColumns={2} to a FlatList, each row's height was pulled up to the taller image, and a large gap opened below its shorter neighbor. Every swipe brought that gap back into view.

For a wallpaper app, the gallery is the shelf itself. A run of gaps reads as "arranged carelessly." I wanted the images packed together like Pinterest, with no wasted vertical space. That is a masonry layout. Here is the order I actually followed as an indie developer, including where I tripped.

Why numColumns leaves a wallpaper gallery looking sparse

FlatList with numColumns groups your data into rows internally. When you place two per row, that row's height is dictated by the taller of the two images. The shorter one is aligned to the top, and space is left below it.

This is not a bug; it is the nature of a grid. If every image shares the same aspect ratio, nothing goes wrong. The moment portrait, landscape, and square are mixed, as wallpapers are, the gaps appear.

The decision point is clear. If your aspect ratios are uniform, a grid is enough. If they vary and a "densely packed" feel is tied to the quality of the experience, it is worth moving to masonry. Wallpapers, photos, and work portfolios are the classic cases.

The idea behind masonry is to stop aligning by rows and instead stack each column independently. With two columns, you place the next image into whichever column is currently shortest. Repeat that, and the two columns naturally converge in height, minimizing the ragged bottom edge.

Have each image's aspect ratio ahead of time

The first thing masonry needs is each image's aspect ratio. If you design it so the ratio is measured only after the image loads at render time, layout becomes two-phase and stutters during scrolling. You cannot assign an image to a column until its height is known.

There is one clean answer: carry the aspect ratio in the data from the start. I store width and height in each wallpaper's metadata and have the list API return them. Adding a column to a CMS or Supabase table is enough.

Where to store itBenefitCaveat
API/DB metadataKnown before render, fastestRequires recording dimensions on ingest
Encoded in the filename or pathNo DB change neededBreaks if the naming convention slips
Measured once and cachedLeaves existing data untouchedLayout shifts only on the first pass

A minimal type is all you need.

type Wallpaper = {
  id: string;
  uri: string;
  width: number;   // source pixel width
  height: number;  // source pixel height
};

You will not use the raw width and height values themselves; only the ratio matters. Still, keeping the source dimensions is convenient later for thumbnail generation and download-size decisions.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
How numColumns creates empty space with variable-height images, and when masonry becomes worth it
A dependency-free column-balancing algorithm that places each image into the shortest column, with working code
Keeping virtualization with FlashList masonry, plus a realistic fallback when you cannot add a dependency
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-07-05
Building a One-Time Code Field in Expo — SMS Autofill and Segmented Display Together
A six-digit verification screen looks trivial, but once you account for SMS autofill, pasting, and deleting one digit at a time, it needs real care. Here is how to nail the iOS and Android autofill first, then build a segmented look on top of a single TextInput that does not break.
App Dev2026-06-27
Before You Ask 'Are You Sure?' — Consider an Undoable Delete
Showing a confirmation dialog every time someone removes a list item trains them to tap OK without reading. Here is how to build an undoable delete in a Rork (Expo) app, and where confirmation dialogs still belong.
App Dev2026-06-27
Your Animation Keeps Running After You Leave the Screen — Focus-Aware Battery Savings in a Rork (Expo) App
In a Rork-generated Expo app, a gradient or breathing animation can keep running after you push another screen or background the app, quietly draining the battery without ever surfacing as jank. The cause is that Reanimated's withRepeat lives on the UI thread and the navigation stack keeps screens mounted. This shows a lifecycle design — useIsFocused plus AppState — that reliably stops off-screen and background loops, with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →