JOUR 73361

Coding the News

Learn how America's top news organizations escape rigid publishing systems to design beautiful data-driven stories on deadline.

Ben Welsh, Adjunct Assistant Professor

Spring 2026

Mondays 6–9 p.m.

Lab 436

Week 3

Building Blocks

How to create and combine reusable components with Svelte

Feb. 9, 2026

Part 1: Introduction to components

Today we're learning to build what web developers call "components." That's the vague term nerds have settled on to describe a reusable piece of code that defines a specific feature. A headline. A button. A chart. An image with a caption. You define it once and then use it everywhere.

Many of today's web frameworks are organized using components so that the different parts of a page can be built in isolation, then assembled together without getting all of the code tangled up.

We see an example of this approach in our class template. Each component arranged in src/routes/+page.svelte is imported from a separate file that defines its own structure, style and behavior. When you put them together, they create the page.

<ArticleHeader
  {headline}
  {byline}
  {pubDate}
/>

<Image
  src="/example-photo.gif"
  alt="The Craig Newmark Graduate School of Journalism is at 219 West 40th Street in Midtown Manhattan."
  caption="The Craig Newmark Graduate School of Journalism is at 219 West 40th Street in Midtown Manhattan."
  credit="Craig Newmark Graduate School of Journalism"
/>

Our template uses a framework known as SvelteKit, which is built on top of the Svelte component system. SvelteKit provides the overall structure for our project, while Svelte gives us the tools to build individual components.

Svelte
https://svelte.dev/
Svelte homepage hero and navigation

Svelte was created in 2016 by Rich Harris while he was living in Brooklyn and working as a graphics editor at The Guardian. His goal was to invent a better way to build interesting pages.

"Newsrooms are a very deadline-driven culture for obvious reasons, and so you need to build things quickly," Harris told an interviewer in 2025. "Svelte was my attempt to make that easier."

He later moved to The New York Times, where he continued to develop Svelte as a tool for building interactive news stories. Today, it is used by newsrooms around the world, including The Guardian, The Times, Reuters, The Pudding and many more.

Svelte is also widely used outside of journalism, in companies like Apple, Square and Spotify. Harris now works on Svelte full-time at the technology company Vercel, which hosts many Svelte projects.

For a real-world example, check out the open-source library released by the Reuters Graphics team. It contains components for common elements like headlines, bylines, captions, embeds, timelines and more.

Reuters Graphics Components
https://reuters-graphics.github.io/graphics-components/
Reuters Graphics components Storybook example

In today's class, we'll stick with the simple set of components I've seeded in our class template. We'll learn how they're structured and then build a few simple ones from scratch. In the weeks to come, we'll get more advanced, building components that fetch data, handle user interactions and integrate with each other.

But let's start with the basics.

Open your browser and navigate to our template repository at github.com/palewire/cuny-jour-static-site-template.

cuny-jour-static-site-template
https://github.com/palewire/cuny-jour-static-site-template
GitHub template repository page showing the Use this template button

Just like last week, click the green "Use this template" button near the top right of the page. This week, let's name our repository something different, like my-first-svelte-components. Make sure "Public" is selected, then click "Create repository."

Now let's get this code onto your computer using the techniques we covered in week one.

In the Explorer sidebar, we're going to navigate to a folder we didn't explore last week, src/lib/components/. You should see a few files there already.

These are Svelte components. Open one of them and take a look. I suggest starting with Image.svelte.

VS Code showing Image.svelte with Explorer open

You'll see three sections:

  1. A <script> block at the top with snippets of JavaScript and what look like inputs
  2. HTML markup in the middle with some braces and tags you might not recognize
  3. A <style> block at the bottom filled with CSS

This three-part structure is the anatomy of every Svelte component. The script handles logic and data. The markup defines the structure. The styles control the appearance.

Seems simple enough, right?

Let's find out by building our own.

Part 2: Creating your first component

We'll start simple. We will build a component that displays a quote in large type, along with the name of the person being quoted.

Before we start coding, we should remember to install the project dependencies. Since we created a new repository for today's work, we'll need to do this step over again.

Open a terminal in Visual Studio Code and run the same command as last week. If you don't have it memorized yet, you will soon because you'll need to execute it every time you start a new project.

npm install

Then start the development server at localhost:5173:

npm run dev

Now we're ready.

Right-click on the src/lib/components/ folder in Visual Studio Code's left-hand Explorer and select "New File." Name it Blockquote.svelte.

The file will open in the editor, completely empty.

VS Code showing an empty Blockquote.svelte file

We'll build this component step by step, starting with the simplest possible version.

Begin by adding a traditional <blockquote> tag to the file, just as you would in a traditional HTML document. Inside it, add a placeholder quote in the <p> tag and a <cite> tag for the attribution.

<blockquote>
  <p>
     As I look back over a misspent life
     I find myself more and more convinced
     that I had more fun doing news reporting
     than in any other enterprise.
     It is really the life of kings.
  </p>
  <cite>— H.L. Mencken</cite>
</blockquote>

This is HTML that you could find in any web page. If we included it in our page, it would render perfectly. But it's not reusable.

If we wanted to display another quote, we'd have to copy this snippet, paste it somewhere else and then change the content, which would duplicate work and open the door to errors and inconsistencies.

We can make our component reusable by replacing the hardcoded quote with a placeholder that accepts input from the outside. This is done using a Svelte feature called $props, which is short for properties. It's how components receive data from the pages where they are put to use.

We can introduce properties to our blockquote by adding a <script> block at the top of the file. We can define variables for as many inputs as we like. They'll be passed in as a JavaScript object via Svelte's magic $props() function, which can be unpacked into separate variables using a technique called destructuring.

<script>
  let { quote, attribution } = $props();
</script>

In this example, I've decided to allow two inputs: quote and attribution, which correspond to the content of the <q> and <cite> tags, respectively. You can name these variables whatever you like. They are just placeholders for whatever data you'd like to have passed in.

Next, we need to tell Svelte to insert these variables into the markup. We do this by replacing the hardcoded text with curly braces {} that reference our variables:

<script>
  let { quote, attribution } = $props();
</script>

<blockquote>
  <p>{quote}</p>
  <cite>{attribution}</cite>
</blockquote>

Before we test it out, let's make it look a little nicer.

Add a <style> tag at the bottom of the file, where we can write CSS. Here's something to get us started. Copy and paste what I've provided. It will use the color scheme defined in our project's app.css file, which is available to all components.

<script>
  let { quote, attribution } = $props();
</script>

<blockquote>
  <p>{quote}</p>
  <cite>{attribution}</cite>
</blockquote>

<style>
  blockquote {
    margin: 2rem 0;
    padding: 1rem;
    padding-left: 1.5rem;
    border-left: 4px solid var(--color-accent);
    background-color: var(--color-light-gray);
  }

  p {
    font-size: 1.125rem;
    line-height: 1.6;
    font-style: italic;
  }

  cite {
    margin: 0.5rem 0 0;
    display: block;
    text-align: right;
    font-size: 0.875rem;
    font-weight: bold;
    font-style: normal;
    color: var(--color-dark-gray);
  }
</style>

These styles will only apply to the blockquote component, and won't affect any other blockquotes or paragraphs on the page. This is a technique that computer programmers refer to as scoping.

By limiting styles to a specific component, we can be confident that our CSS will not have unintended consequences elsewhere in our project. It's another way that components help us write clean, maintainable code.

Save your Blockquote.svelte file and we're ready to see it in action.

Open src/routes/+page.svelte in your editor.

At the top of the file, add an import statement inside the existing <script> block, which will bring our new component into this page.

import Blockquote from '$lib/components/Blockquote.svelte';

The $lib path is a special alias in SvelteKit projects that points to your src/lib folder. It saves you from writing long relative paths like ../../lib/components/.

Now scroll down to the main content area of the page, inside of the ArticleBody component. This is where the content of our page lives. We can add our blockquote here between the existing paragraph tags. Feel free to put in any quote you like, or use the one I provided below.

<Blockquote
  quote="As I look back over a misspent life I find myself more and more convinced that I had more fun doing news reporting than in any other enterprise. It is really the life of kings."
  attribution="H.L. Mencken"
/>

Save the file and check your browser at localhost:5173. You should see a styled quote box with a left border and gray background.

localhost:5173
http://localhost:5173
Local development page centered on the blockquote component

Now add a second Blockquote with different text.

<Blockquote
  quote="Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances."
  attribution="Constitution of the United States"
/>

Save the file and check your browser again. You should see two blockquotes, each with different content but identical styling.

localhost:5173
http://localhost:5173
Local development page showing two blockquotes

That's the power of components. You define it once and then use it everywhere. If you want to change the design, you only have to change it in one place, and all instances of that component will update automatically.

Take five to ten minutes to customize your blockquote. Try changing the border color, the background, the font size. Each change you make will update every blockquote on the page.

Try asking Copilot for edits.

Open the chat panel and ask it to "Make the blockquote font bigger" or "Make the blockquote background a light blue" or "Make the blockquote border dashed instead of solid." Save the results and see how it did.

Then try something broader like, "Make this blockquote component look like a 19th century newspaper."

You will see the AI assistant is able to understand the structure of your component and make changes big and small. These new tools can help you quickly iterate and explore variations without having to write every line of code yourself.

Part 3: Creating a parent component

Components can be about more than just displaying content. They can also be about layout and organization, wrapping other components in a larger design.

A simple example of this in our template is the ArticleBody component, which wraps the main content of our page. It doesn't have any props of its own, but it provides a consistent look whatever content we put inside it.

VS Code showing ArticleBody.svelte component with Explorer open

Rather than accept traditional properties, ArticleBody accepts "children," which is a special prop that contains whatever is placed between the opening and closing tags of the component in your page.

For example, in src/routes/+page.svelte, we have something like this:

<ArticleBody>
  <p>...</p>
  <Blockquote ... />
  <p>...</p>
</ArticleBody>

The interior content is placed inside the component by the {@render children()} function call.

To demonstrate the power of this pattern, let's build a new component that wraps other components in a horizontal row.

We'll use it to package a group of statistics pumped up in larger type, like the big, stylized numbers you see on top of data dashboards or mixed into graphics stories.

Create a new file at src/lib/components/BigNumber.svelte. Add this code I've created as a starting point.

<script>
  let { number, label, footnote } = $props();
</script>

<div class="big-number">
  <span class="number">{number}</span>
  <span class="label">{label}</span>
  {#if footnote}
    <span class="footnote">{footnote}</span>
  {/if}
</div>

<style>
  .big-number {
    text-align: center;
    padding: 1.5rem;
    background-color: var(--color-accent);
    color: white;
    border-radius: 8px;
  }

  .number {
    display: block;
    font-size: 3rem;
    font-weight: bold;
    color: var(--color-white);
    line-height: 1.2;
  }

  .label {
    display: block;
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: 0.5rem;
  }

  .footnote {
    display: block;
    font-size: 0.75rem;
    color: var(--color-white);
    margin-top: 0.25rem;
    font-style: italic;
  }
</style>

Notice the {#if footnote} line. That's a new trick.

The {#if ...}{/if} syntax is one of Svelte's main ways of dealing with different variations of a component.

In this case, I wanted to make the footnote optional. Some components will have it, others won't, and I want a component that can handle both situations.

By nesting the footnote in an {#if} block, Svelte will only render that part of the markup if a footnote property is provided. If it's not provided, the component will simply skip that section and render the rest.

Let's try it out on our page.

Open src/routes/+page.svelte again. Add another import at the top:

import BigNumber from '$lib/components/BigNumber.svelte';

Then add these three big numbers, just below the Image and above the ArticleBody.

<BigNumber
  number="2006"
  label="Year Founded"
/>

<BigNumber
  number="1,300"
  label="Alumni"
/>

<BigNumber
  number="50%"
  label="Attend tuition free"
  footnote="As of Aug. 2025"
/>

Save and check your browser. You should see three blue boxes with white numbers, stacked vertically.

localhost:5173
http://localhost:5173
Local development page showing three BigNumber components in a row

It's a start, but they'd look better side by side in a three-column layout.

That's where our parent component comes in. We'll call it Dashboard, and it will be responsible for arranging its children in a horizontal row with some spacing.

Create a new file at src/lib/components/Dashboard.svelte. Drop this in:

<script>
  let { children } = $props();
</script>

<div class="row">
  {@render children()}
</div>

<style>
  .row {
    margin: 2rem 0;
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    justify-content: center;
  }

  .row > :global(*) {
    flex: 1;
    min-width: 200px;
    max-width: 300px;
  }
</style>

Just like ArticleBody, this component accepts children as a prop, which will contain whatever we place between the opening and closing tags of the Dashboard component in our page.

The markup is simple. We wrap the children in a <div> with a class of "row," which we will style using CSS to create a horizontal layout.

One important extra bit of CSS is the :global(*) selector (where the asterisk means "all elements"). This is a special Svelte syntax that allows us to target child elements of the row, even though they are technically outside the scope of this component. This is necessary to apply the flex properties to the BigNumber components inside.

Save the file and head back to src/routes/+page.svelte. Add an import for the Dashboard component:

import Dashboard from '$lib/components/Dashboard.svelte';

Now wrap your BigNumber components in a Dashboard:

<Dashboard>
  <BigNumber
    number="2006"
    label="Year Founded"
  />

  <BigNumber
    number="1,300"
    label="Alumni"
  />

  <BigNumber
    number="50%"
    label="Attend tuition free"
    footnote="As of Aug. 2025"
  />
</Dashboard>

Save and check your browser. The three stat boxes should now sit side by side in a neat row.

localhost:5173
http://localhost:5173
Local development page showing three BigNumber components arranged horizontally in a Dashboard

Much better!

This pattern of components containing other components is called composition. You can nest them as deep as you want. A Page might contain Sections. A Section might contain Rows. A Row might contain Cards. Each level handles one concern, making the code easier to understand and modify.

Part 4: Prompting AI to build components

Now that you've seen how simple components are to build, why not let AI have a try?

It might seem strange at first, but you can prompt an AI assistant to build a component for you simply by describing what you want in plain language. And the results can be surprisingly good.

Open the chat panel in Visual Studio Code and ask your AI assistant to "Build a Svelte component that can serve as a promotional aside box in the middle of our article body. It should have a title, some text and a call to action button. Implement it in our page as callout that the Newmark school is now accepting applications for the next class with a link to https://www.journalism.cuny.edu/future-students/admissions/how-to-apply/."

localhost:5173
http://localhost:5173
Local development page showing AI-generated promotional aside component

Within a few seconds, you should see a completed component appear on your page. Like mine, it may not be perfect, but it should be a solid starting point that you can customize and improve.

Rather than editing the code yourself, try asking the AI assistant to make small improvements. For example, you could say, "Remove the gradient from the background" or "Make the button square instead of rounded."

After a few iterations, you should have a component that looks and functions the way you want, without having to write every line of code yourself. Here's how mine turned out after a few rounds of prompting.

localhost:5173
http://localhost:5173
Local development page showing AI-generated promotional aside component after refinement

Homework

Task 1: Build three new components

Create a new repository based on our class template and add three components that could be useful in a news story. They should be different from the ones we built in class. Get them working on a page together, and deploy it via GitHub. Send me the link.

Here are some ideas. You should come up with at least one on your own.

  • An author bio box with photo, name, title, and social links
  • A key takeaways box that lists bullet points in a highlighted container
  • A card that links out to a related story with a thumbnail and headline
  • A correction or update box for editor's notes
  • A methodology section that explains where the data sources and analysis methods in the story
  • A diptych parent component that places two related images side by side with captions
  • A timeline component that lists events in chronological order with dates and descriptions
  • A profile card for a person or organization mentioned in the story
  • A breaking news alert bar that banners across the top of the page with a headline and link
  • A group of share buttons for social media platforms like Twitter, Facebook and LinkedIn
  • A component to embed YouTube videos

Task 2: Prepare to present

Pick one of your original components to present in class next week. Send me a link to the live page, as well as a link to the code behind it.

Be prepared to:

  • Show the component and code to the class
  • Explain how it works. If you had AI assistance writing the code, you should do whatever research is necessary to understand it well enough to explain it in your own words.
  • Discuss how you used AI and share what worked, what didn't, and what you learned from the process