Decisions
How it looks
The grid copies the search results customers know from bilder.ntb.no.IG1
bilder.ntb.no is the site customers know. Measured in Chrome at 2560 and 2160 px wide windows. Interactive tiles (hover Download and Add, select checkbox) follow im-web's public search.
Source: https://bilder.ntb.no/r/?mainGroup=creative, 2026-10-08 · https://public.im.test.ntb.dev/search
Every row of pictures has the same height, in both layouts.IG2
Rows of different heights aren't needed, and equal rows make it simple to draw only what's on screen.
Pictures fill each row edge to edge, cropped only a little, and rows only rearrange when the window changes a lot.IG3
Packing by shape rather than pixels means a resize only scales the rows until the shape steps. Crops stay small because a row is never more than half an image from its target.
A short last row isn't stretched, and a panorama gets a row of its own.IG4
A few leftovers shouldn't blow up to huge sizes, and a panorama shouldn't squash its row into a sliver.
In the grid view each picture sits whole in a square, with the same caption space under every one.IG5
Keeps rows equal; the caption space is the same at every size.
One slider sets the picture size, and the size stays when you switch layout.IG6
bilder.ntb.no has one slider for both views.
The grid follows the site's colours and light or dark mode, like every other page.IG7
bilder.ntb.no's fixed dark colours were dropped on request: the grid has to fit this site.
Pictures have slightly rounded corners, and a keyboard user sees a clear ring around the one in focus.IG14
A ring drawn by the tile itself would sit under the image, so it is its own layer on top.
When there are no pictures, the page decides what to say, not the grid.IG12
What to say depends on the search, which the grid doesn't know about.
How it loads
Up to 200 pictures are all drawn at once; beyond that only the rows on screen are drawn.IG8
Small results need no virtualization, and the browser already skips painting off-screen rows. Switching between the two moves nothing, since both use the same row height and gap.
More pictures load before you reach the bottom, and each page of results is fetched only once.IG9
The virtualizer alone missed jumps made by setting scrollTop from a script. React Aria's sentinel asked twice for the same page, which added its images twice.
Thumbnails use the smallest preview, and the first screen loads first.IG10
Thumbnails loaded slowly. The 256 px preview is 30–60 KB against 100–170 KB for 512; the preview server already uses HTTP/2 and long caching, so file size was the problem.
The demo fakes a search over our sample pictures, and can pretend there are thousands of them.IG11
Feels like a real search without a backend. Repeated images come from the browser cache, so image timings at large totals look better than they would be.
Engine
The grid runs on TanStack; the React Aria version is kept only to show and compare.IG13
React Aria pays off when its whole component system is used, not for one component: here it cost about 6× the DOM and up to 8× the responsiveness delay (INP), and its up/down keys needed our own handler in justified rows.
Not built
Selecting pictures, moving with the keyboard and screen-reader support are not built in the TanStack grid yet.OpenIG-open-1
The React Aria version has them.
The margins don't adapt to screen width yet; the demo uses 20 px everywhere.OpenIG-open-2
Coming back from a picture's page doesn't return you to where you were in the grid yet.OpenIG-open-3
The first rows are not drawn on the server yet, so pictures appear only after the page has loaded.OpenIG-open-4
The grid needs the measured width to lay out rows.