JOUR 73361
Coding the News
Learn how America's top news organizations escape rigid publishing systems to design beautiful data-driven stories on deadline.
Multimedia Slideshow
How to build a full-screen, mobile-first photo essay
Apr. 13, 2026
From the square crop to the selfie and now the vertical video, social media sites like Instagram, Snapchat and TikTok have had a huge influence on how the world consumes photographs and video.
They've done more than change the way we make images. They've changed the way we use them to tell stories.
One straightforward example is the photo essay. While photo galleries are far from new — Life magazine was publishing them in the 1930s — the format has evolved online.
The photo essays we see on social media are designed for phones, with one image filling the screen at a time, and navigation that encourages tapping through. They're not just a collection of images — they're a sequence. When they're well done, they're also a story.
News organizations have taken a page from social media's playbook and designed story formats that emulate social media designs.
One example is "Forty Hours Later, a Shot at a Vaccine," a 2021 photo essay from The New York Times that documented the early COVID vaccine rollout in Kentucky.
It's just one example of how news websites and apps are experimenting with new formats that borrow from social media.
When done well, what distinguishes these stories from amateur creators is the craft and care that goes into them. Unlike the "photo dumps" of social media, professional galleries arrange high-quality images in a deliberate sequence.
Today you will see how professionals build these stories, and then you'll build your own. Our demonstration case will be a set of photographs taken by a famous photojournalist whose work has entered the public domain.
Part 1: Introducing Gordon Parks
Gordon Parks was one of the most celebrated photojournalists of the twentieth century. Born in Kansas in 1912 as the last of 15 children, he rose from poverty to become a photographer for Vogue and Life, covering the civil rights movement, celebrity, fashion and the everyday lives of people across America. He went on to become a filmmaker, directing the iconic blaxploitation film "Shaft."
In 1943 Parks started photographing daily life in Harlem for a New Deal government program that hired artists to document American life. His intimate and humanizing photographs of New York's most famous black neighborhood challenged the stereotypes that dominated portrayals of Harlem and are recognized as some of the most notable images of the era.
Because that work was produced for the federal government, they're also part of the public domain and free for anyone to reuse.
Today you're going to arrange a selection of these photographs into a story. But you're not going to make a traditional article with text and images stacked in a column. Instead, you're going to make a full-screen, mobile-first photo essay that looks and feels like the forms that dominate social media.
Set up your project
As in past weeks, open your browser and navigate to our template repository at github.com/palewire/cuny-jour-static-site-template.
Click "Use this template" and create a new repository called parks-harlem-gallery. Clone it. Open it in Visual Studio Code.
Install the dependencies.
npm installStart the test server.
npm run devTo get us started, I've seeded the template with the photographs you'll use, downloading them from the Library of Congress's digital collection.
You'll find them at static/photos/. The file structure should look like this:
static/
├── photos/
│ ├── gordon-parks-portrait.jpg
│ ├── parks-harlem-01.jpg
│ ├── parks-harlem-02.jpg
│ ├── parks-harlem-03.jpg
│ ├── parks-harlem-04.jpgPart 2: Getting a gallery started
Just like last week, you're going to lean on the pre-built components in the class template's Storybook. Open it now and look at what's available for this project. You'll see the starter kit in the "composition" for a Multimedia Gallery.
The demonstration there is created by combining a set of five components into a unified design. Each component has a specific role to play.
| Component | Description |
|---|---|
SlideGallery | The container that manages navigation, dots, and the horizontal slide track |
TitleSlide | The opening card with headline, intro, and byline |
PhotoSlide | A full-screen image with a caption overlay |
TextSlide | A full-screen text card for chapter breaks |
CreditsSlide | The closing card for attribution and methodology |
Click through each one in Storybook. Use the controls panel to experiment with the properties. This is exactly how you'd explore a component library at a real newsroom.
Now open src/routes/+page.svelte and clear the boilerplate. Then toss in the SlideGallery and TitleSlide imports from Storybook, which will serve as the start of our story.
<script>
import SlideGallery from '$lib/components/MultimediaGallery/SlideGallery.svelte';
import TitleSlide from '$lib/components/MultimediaGallery/TitleSlide.svelte';
</script>
<SlideGallery>
<TitleSlide
headline="Harlem, 1943"
intro="How the street photography of Gordon Parks captured the spirit of black New York"
/>
</SlideGallery>Save and check your browser. The full-screen title card should already be there, with your gallery layout taking over the page.
There's only one problem. While our gallery is looking good, it's sandwiched between the default header and footer. To break out of the standard page layout, we need to follow the pattern of previous weeks and deactivate the standard stuff in src/routes/+page.js.
// Page settings
// These values are passed to the layout to control what appears on the page.
export function load() {
return {
// Set to false to hide the NYCity News Service header
showHeader: false,
// Set to false to hide the site footer
showFooter: false,
};
}Part 3: Looping through the story
Rather than hardcoding every slide into the Svelte template, a common technique in newsrooms is to separate the content from code by storing each slide's information in a data file.
The Svelte code can then loop through the file and render the right component for each entry, just like it looped through database records in previous lessons.
Create src/lib/data/story.json and start with these two entries. They will serve as the first and second slides in our story.
[
{
"id": 1,
"type": "photo",
"filename": "parks-harlem-01.jpg",
"title": "The newsboys",
"caption": "A frequent subject were the neighborhood's newsboys: children who hawked papers on the street corners.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 2,
"type": "photo",
"filename": "parks-harlem-02.jpg",
"title": "Standing tall",
"caption": "Parks returned to them again and again, often capturing their portraits from below, tilting his camera upward to lend the boys a proud stature.",
"credit": "Gordon Parks / Library of Congress"
}
]Now update src/routes/+page.js to import the data file and pass it to the page, following the same pattern from Week 9. Keep the header and footer settings from earlier:
import story from '$lib/data/story.json';
export function load() {
return {
showHeader: false,
showFooter: false,
story,
};
}Then update +page.svelte to receive the data, import PhotoSlide and loop through the entries, passing each record from the JSON file to the component.
<script>
import SlideGallery from '$lib/components/MultimediaGallery/SlideGallery.svelte';
import TitleSlide from '$lib/components/MultimediaGallery/TitleSlide.svelte';
import PhotoSlide from '$lib/components/MultimediaGallery/PhotoSlide.svelte';
let { data } = $props();
</script>
<SlideGallery>
<TitleSlide
headline="Harlem, 1943"
intro="How the street photography of Gordon Parks captured the spirit of black New York"
/>
{#each data.story as slide (slide.id)}
<PhotoSlide photo={slide} />
{/each}
</SlideGallery>Save and check your browser. The two photos from the JSON file should now be part of the gallery, appearing after the title card.
That's a nice start to our gallery, but it lacks what any story needs: A clear beginning. Our slides are tossing the reader in without any orientation. Who is Gordon Parks? Why was he in Harlem?
We should establish that first by inserting a text slide before the photos that gives the reader some context before we zero in on the newsboys.
Replace your story.json file with this updated version, which adds a new "text" entry at the beginning of the story and a more general street scene as the second slide.
[
{
"id": 1,
"type": "text",
"headline": "The Assignment",
"body": "In 1943, the photographer Gordon Parks was assigned to document life in Harlem — New York's most famous black neighborhood — by a federal government program that employed artists during the Great Depression."
},
{
"id": 2,
"type": "photo",
"filename": "parks-harlem-03.jpg",
"title": "Shooting the streetscape",
"caption": "He spent weeks photographing residents from all walks of life. He shot his subjects as he found them: Sitting on stoops, leaning against fences, hanging out windows.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 3,
"type": "photo",
"filename": "parks-harlem-01.jpg",
"title": "The newsboys",
"caption": "A frequent subject were the neighborhood's newsboys: children who hawked papers on the street corners.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 4,
"type": "photo",
"filename": "parks-harlem-02.jpg",
"title": "Standing tall",
"caption": "He returned to them again and again, often capturing their portraits from below, tilting his camera upward to lend the boys a proud stature.",
"credit": "Gordon Parks / Library of Congress"
}
]Now update +page.svelte to handle the new "text" type by adding a TextSlide import and an {:else if} branch.
<script>
import SlideGallery from '$lib/components/MultimediaGallery/SlideGallery.svelte';
import TitleSlide from '$lib/components/MultimediaGallery/TitleSlide.svelte';
import PhotoSlide from '$lib/components/MultimediaGallery/PhotoSlide.svelte';
import TextSlide from '$lib/components/MultimediaGallery/TextSlide.svelte';
let { data } = $props();
</script>
<SlideGallery>
<TitleSlide
headline="Harlem, 1943"
intro="How the street photography of Gordon Parks captured the spirit of black New York"
/>
{#each data.story as slide (slide.id)}
{#if slide.type === 'photo'}
<PhotoSlide photo={slide} />
{:else if slide.type === 'text'}
<TextSlide {slide} />
{/if}
{/each}
</SlideGallery>This type of "casing," which renders different components based on a type field in the data, is a common pattern in news applications. It allows you to mix photos, text, videos, embeds, graphics in a single feed.
Save and tap through. The text card should now appear with a different layout than the photos.
Part 4: Finishing the job
Every story needs a conclusion. We'll end ours by capturing the Harlem project's legacy and sharing a photo of Parks himself.
Open story.json one more time and add the final three entries.
[
{
"id": 1,
"type": "text",
"headline": "The Assignment",
"body": "In 1943, the photographer Gordon Parks was assigned to document life in Harlem — New York's most famous black neighborhood — by a federal government program that employed artists during the Great Depression."
},
{
"id": 2,
"type": "photo",
"filename": "parks-harlem-03.jpg",
"title": "Shooting the streetscape",
"caption": "He spent weeks photographing residents from all walks of life. He shot his subjects as he found them: Sitting on stoops, leaning against fences, hanging out windows.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 3,
"type": "photo",
"filename": "parks-harlem-01.jpg",
"title": "The newsboys",
"caption": "A frequent subject were the neighborhood's newsboys: children who hawked papers on the street corners.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 4,
"type": "photo",
"filename": "parks-harlem-02.jpg",
"title": "Standing tall",
"caption": "He returned to them again and again, often capturing their portraits from below, tilting his camera upward to lend the boys a proud stature.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 5,
"type": "photo",
"filename": "parks-harlem-04.jpg",
"title": "Shining a light",
"caption": "The photos were not widely published at the time, but they helped Parks establish the humane approach to African-American subjects that became his signature.",
"credit": "Gordon Parks / Library of Congress"
},
{
"id": 6,
"type": "photo",
"filename": "gordon-parks-portrait.jpg",
"caption": "In the years that followed, Parks went on to shoot for Vogue, Life and many other mainstream publications, before becoming a Hollywood director and creating movies like \"Shaft.\"",
"credit": "Rowland Scherman / National Archives"
},
{
"id": 7,
"type": "text",
"body": "After Parks died in 2006, some of the Harlem portraits were enshrined with his other work at Manhattan's Museum of Modern Art."
}
]All that's missing now is the closing card, where we can credit ourselves and share any additional information about the story's sources and methods.
That's where the special CreditsSlide comes in. Like the MethodologyBox we used in earlier lessons, it follows the pattern of a parent component that expects "children" to be passed in as content.
<script>
import SlideGallery from '$lib/components/MultimediaGallery/SlideGallery.svelte';
import TitleSlide from '$lib/components/MultimediaGallery/TitleSlide.svelte';
import PhotoSlide from '$lib/components/MultimediaGallery/PhotoSlide.svelte';
import TextSlide from '$lib/components/MultimediaGallery/TextSlide.svelte';
import CreditsSlide from '$lib/components/MultimediaGallery/CreditsSlide.svelte';
let { data } = $props();
</script>
<SlideGallery>
<TitleSlide
headline="Harlem, 1943"
intro="How the street photography of Gordon Parks captured the spirit of black New York"
/>
{#each data.story as slide (slide.id)}
{#if slide.type === 'photo'}
<PhotoSlide photo={slide} />
{:else if slide.type === 'text'}
<TextSlide {slide} />
{/if}
{/each}
<CreditsSlide>
<h2>About These Photographs</h2>
<p>
The Harlem negatives are held by the Library of Congress and are free for anyone to
download and reuse. High-resolution scans are available in the
<a href="https://www.loc.gov/pictures/search/?q=gordon+parks+harlem&co=fsa" target="_blank">
Prints & Photographs Division</a>'s digital collection.
</p>
</CreditsSlide>
</SlideGallery>Save and tap all the way through. You now have a complete story with a beginning, a middle, and an end.
Part 5: Testing on your phone
We've been building on a desktop browser, but the whole point of this design is to prioritize mobile users. Let's see how it actually looks on a phone screen.
You don't need to pull out your phone to test this. Every major browser has a built-in tool that simulates a mobile device right on your desktop.
Open the developer tools in your browser. Then click the device toggle button in the top panel — it looks like a phone and tablet overlapping.
The browser will reframe your page inside a simulated phone screen. Use the dropdown at the top to pick a specific device or set a custom width. The page will re-render at that size, giving you a realistic preview of the mobile experience.
Tap through the gallery in this view. Each slide should snap into place in the different mobile layout.
Save, commit, and push to GitHub. Before you push, remember to visit your repository's settings to activate GitHub Pages. Watch the Actions tab to confirm the deploy succeeds, then open your live site on your phone and test it on the real thing.
Homework
Task 1: Build your own photo story
Find a collection of public domain photographs from a digital archive and build your own full-screen photo essay. Good sources:
| Source | Description |
|---|---|
| Library of Congress | The nation's oldest federal cultural institution, with millions of photographs in the public domain |
| NYPL Digital Collections | New York Public Library's archive of maps, prints, photographs, and manuscripts |
| Smithsonian Open Access | The world's largest museum and research complex, with millions of free images |
| Wikimedia Commons | A community-curated media repository with freely licensed and public domain files |
| National Archives | The US government's official repository of federal records and photographs |
Your project must have:
- A
TitleSlidewith a headline and introduction - At least 4
PhotoSlideentries with caption overlays - At least two
TextSlideentries that provide editorial context - A
CreditsSlidewith source information and proper attribution - A clear narrative arc with a beginning, middle and end
- One small twist on the design, like a color change or new feature
Deploy it to GitHub Pages and send me the link.
Task 2: Prepare to present
You should be prepared to:
- Show the story in a narrow browser window
- Explain your editorial choices
- Discuss how you found your source and verified that you had the rights to republish the photos










