Images & Media

An image is never just decoration. It establishes credibility, creates a sense of place, shows something that words can’t, or gives a reader a moment to breathe between ideas. How you place an image shapes how it functions—whether it commands the full page, sits beside an argument, or anchors a section.

Xanthan gives you a small set of placement tools, each suited to a different editorial intention. That’s deliberate: rather than infinite options that require infinite decisions, you have a few choices that each do something well. Knowing what each one does is how you choose intentionally.


Stand-alone images

Use the figure.html include to place a single image in your content. It’s always centered and sits on its own—it gets its own visual moment, and whatever follows starts cleanly below it. You decide how big it is with image-width.

{% include images/figure.html
  image-width="60%"
  caption="What a nice view"
  alt-text="Hiking trails winding through a canyon in the Sandia foothills."
  image-path="/assets/images/backgrounds/hike-1.jpg"
%}
Hiking trails winding through a canyon in the Sandia foothills.

What a nice view

Choosing a width

image-width accepts any CSS width. Two common choices:

Full-width (image-width="100%") — the image commands the entire content column. The most common choice for photographs, maps, or anything that deserves full attention. Make sure your source image is at least 1200px wide to avoid looking grainy.

A full-width image commands the full content column.

A full-width image commands the full content column.

A smaller centered width — good when the image doesn’t need to fill the full column, or when a smaller image would look lost at full width.

Centered at 60% width.

Centered at 60% width.

When you want an image beside text rather than above or below it, use figure-wrap.html—the recommended tool for pairing an image with text. See Image alongside text below; that’s also how you offset an image to one side of the page.

Parameters

Parameter Options What it does
image-path File path Path to the image file
image-width Any CSS value (40%, 300px) Width of the image (default: 40%)
caption Text Caption displayed below the image
alt-text Text Accessibility description (falls back to caption)

Image alongside text

Sometimes an image and a passage of text belong together as equals—neither subordinate to the other, both necessary to the point. Use figure-wrap.html to place them side by side as an explicit two-column pair.

{% include images/figure-wrap.html
  image-path="/assets/images/backgrounds/hike-1.jpg"
  caption="What a nice view"
  alt-text="Hiking trail through a canyon in the Sandia foothills."
  image-position="right"
  image-width="45%"
  text="The text you put here sits beside the image, not below it. Good for introductory paragraphs, a key argument tied to a specific image, or whenever the visual and the words need to be read together. Content that follows the include starts at full width below both columns."
%}
Hiking trail through a canyon in the Sandia foothills.

What a nice view

The text you put here sits beside the image, not below it. Good for introductory paragraphs, a key argument tied to a specific image, or whenever the visual and the words need to be read together. Content that follows the include starts at full width below both columns.

The design forces a useful editorial question: which text belongs paired with this image? That’s a decision worth making deliberately.

Image on the left

With image-position omitted (or set to left), the image sits on the left:

Left-aligned image at 35% width. The text column takes the remaining space. Markdown is supported in the text parameter—bold, italic, links, and even line breaks.

Long text stays in its column

When the text is longer than the image is tall, it continues in the text column—it does not flow underneath the image. Whatever comes after the include starts at full width below both columns.

This paragraph is long enough to extend past the bottom of the image. Notice that the text stays in its own column throughout—it doesn’t wrap under the image the way a floated image would behave. The two columns are independent. When the text runs long, the image column simply ends at the image bottom and leaves empty space below it. This is predictable, intentional behavior: you know exactly what you’re getting. The content that follows this include will start at full width, below the taller of the two columns.

This is content after the figure-wrap. It starts at full width below both columns.

Parameters

Parameter Options / Default What it does
image-path (required) Path to the image file
text (required) Text beside the image; supports Markdown
caption (none) Caption below the image; supports Markdown
alt-text Falls back to caption Accessibility description for screen readers
image-position left (default), right Which side the image appears on
image-width 40% (default) CSS width of the image column

On small screens, the layout collapses to a single column with the image on top.

Both image includes share image-width, so sizing works the same way whether an image stands alone or sits beside text. image-position is unique to figure-wrap—a standalone figure.html image is always centered, so there’s no side to choose.


Alt-text


Jumbotron images

A jumbotron breaks out of the page’s content column to fill the full browser width—the most cinematic option available. Use one as a visual break between sections, or add a text overlay. The gradient fade is applied automatically so text reads cleanly against the image.

{% include images/jumbotron.html
  image-path="/assets/images/backgrounds/pano-1.jpg"
  height="50vh"
  box-align="left"
  title="Optional heading"
  text="A pull quote or caption on the clear side of the image."
  background-position="center right"
%}

Optional heading

A pull quote or caption on the clear side of the image.

No text or fade

If you omit text and title, the gradient is skipped and the jumbotron renders as a clean full-width visual break—useful as section dividers on long pages.

{% include images/jumbotron.html
  image-path="/assets/images/backgrounds/pano-1.jpg"
  height="30vh"
  background-position="center 60%"
%}

Heading, zoom, and custom fade

Use zoom to enlarge the image and fade-start/fade-end to control how quickly the gradient appears and disappears.

Landscape at dusk

{% include images/jumbotron.html
  image-path="/assets/images/backgrounds/pano-1.jpg"
  height="50vh"
  box-align="right"
  title="Landscape at dusk"
  zoom="150%"
  background-position="center left"
  fade-start="10%"
  fade-end="80%"
%}

Parameters

Parameter Default What it does
image-path (required) Path to image
height 40vh CSS height, e.g. 50vh or 400px
box-align left left, right, or center — text position and gradient direction
title (none) Heading above the text
text (none) Body text; supports Markdown. Gradient applied automatically when present
zoom cover CSS background-size; e.g. 150% to zoom in
background-position center CSS background-position; aim the subject away from the text side
fade-start 0% Where the opaque gradient begins
fade-end 60% Where the gradient becomes fully transparent
bg-color var(--bg-page) Color of the opaque side; matches page background by default
caption (none) Caption below the image; supports Markdown

box-align controls both text placement and gradient direction:

box-align Text position Gradient
left (default) Left side Opaque left → transparent right
right Right side Opaque right → transparent left
center Centered over image No gradient; text shadow used for legibility

Header images

A header image is a large banner across the top of the page, set in the front matter rather than in the content body:

---
title: My Page Title
layout: default
header-image: /assets/images/backgrounds/pano-1.jpg
---

Controlling height and position

---
title: My Page Title
layout: default
header-image: /assets/images/backgrounds/pano-1.jpg
header-height: 50vh
header-position: center
---
Field Default What it does
header-height 40vh Height of the header (e.g. 50vh, 300px)
header-position (browser default) Which part of the image to show (center, top, 200px, 50% 80%)

header-position is particularly useful when you have a large image but only a specific part—the sky, the bottom half—is what you want visible in the header crop.


Before/after slider (Juxtapose)

Compare two images with a draggable slider—ideal for historic vs. contemporary photos, before/after transformations, or any visual comparison where the reader should be able to move between the two states themselves.

{% include images/juxtapose.html
  image1="/scrollstories/forest/images/mvh-tv-room.jpg"
  image2="/scrollstories/forest/images/mvh-hist-common-room.jpg"
  caption="The TV room becomes the History Department Common Room."
%}
From the TV room to the Chair room (actually, the History Department Common Room). With a less good view of the mountains. From the TV room to the Chair room (actually, the History Department Common Room). With a less good view of the mountains.

From the TV room to the Chair room (actually, the History Department Common Room). With a less good view of the mountains.

Parameter What it does
image1 Path to the first (left) image
image2 Path to the second (right) image
caption Optional caption below the slider
starting-position Where the slider starts (default: 50%)

The effect is most striking when the two images are taken from the same vantage point.


Display a series of images as a slideshow. Define three lists (images, headers, captions) and pass them to the carousel include:

{% assign images =
"/scrollstories/forest/images/mvh-construction.jpg,
/scrollstories/forest/images/mvh-room-cost.jpg,
/scrollstories/forest/images/mvh-tv-room.jpg" | split: ','
%}

{% assign headers =
"A Photo Title,,
No caption here" | split: ','
%}

{% assign captions =
"It's useful to have informative captions|
This image has a caption, but no title|
" | split: '|'
%}

{% include images/carousel.html
  width="80%"
  class="center"
  images=images
  headers=headers
  captions=captions
%}
Parameter What it does
width Width of the carousel (default: 100%)
class Alignment: left, center, right
id Unique ID if you have multiple carousels on one page
images List of image paths
headers List of slide titles (use empty values for no title)
captions List of slide captions (split on \| to allow commas in text)

ScrollStory images

For more cinematic image effects, ScrollStories offer techniques where images stay fixed while text scrolls over them, backgrounds switch as the reader advances, and panels slide horizontally alongside fixed visuals. These go beyond standard image placement into full narrative design.

See the ScrollStories overview to learn what’s available, or jump directly to Peekaboo images for a parallax-style reveal effect that works on any page.


Making your images smaller

A photograph straight from a phone or a museum download is often 4000 pixels wide and several megabytes. Nothing on a web page needs that, and a page full of them is slow to open on a phone — which is where a good share of your readers are. Shrinking them changes nothing a reader can see.

You do not need a command line for this. Your site ships with a job that does it for you:

  1. Go to the Actions tab of your repository on GitHub
  2. Choose Optimize Images in the left-hand list
  3. Click Run workflow, and check the settings:
Setting What it means
Folder Which folder to work through. assets/images covers your site’s shared images; an essay’s own folder works too, like essays/my-essay/images
Longest edge The biggest either side is allowed to be. 1600 suits a full-width image; use 2400 for something meant to be looked at closely
Quality JPEG quality out of 100. 85 is a good default. Below about 70 you start to see it
Actually change the files Leave this unticked the first time
  1. Click the green Run workflow button

The first run changes nothing. It reports what it would do — file by file, with the size before and after — in the run summary. Read that, and if the numbers look right, run it again with Actually change the files ticked.

When it runs for real it commits the smaller images for you, and your site rebuilds a minute or two later. Your originals are still in the repository’s history if you ever want them back.

It also converts PNGs to JPGs where that saves a lot and the image has no transparency — photographs saved as PNG are often several times larger than they need to be. When it does, it updates the references in your pages to match, so nothing breaks.

If you do work at a command line, the same thing runs locally:

bash scripts/optimize-images.sh --preview --recursive --base-dir assets/images

Drop --preview to apply it. bash scripts/optimize-images.sh --help lists the rest of the options.


Troubleshooting


Embedding media

Beyond images, you can embed audio, video, and 3D models. In most cases you copy an “embed code” from a service like YouTube or SketchFab and paste it onto your page. For a few formats Xanthan provides an include so the markup stays standardized and easy to maintain.

Sound files

To embed a sound file with a simple playback interface, use the audio.html include and set src to the path of your file.


{% include media/audio.html
  src="/assets/audio/sample-audio.mp3"
%}

Embed codes from other services

For YouTube, Vimeo, SketchFab, and similar services, copy the embed code the service gives you and paste it directly into your page. It will render as-is.

Image doesn’t appear:

  • Check that the file path and filename match exactly (paths are case-sensitive)
  • Verify the image file has been committed to your repository
  • Make sure the path starts with / for absolute paths or is correct relative to the current page

Image looks grainy or stretched:

  • Use images large enough for the display width (at least 1200px wide for full-width images)
  • JPG works well for photographs; PNG is better for diagrams or screenshots with text

Header image not showing:

  • Ensure header-image is in the front matter block (between the --- lines), not in the page content
  • Check the path carefully—it should start with /

For more help, see Troubleshooting.