ScreenshotNeo

BlogHow-to

How to Position Text Over an Image with CSS

Position image overlay text with CSS using a relative wrapper and an absolute overlay. Learn when to use an <img> or background, keep text readable, and handle responsive layouts.

By the ScreenshotNeo team4 October 20266 min read

To position text over an image, put both in the same wrapper, set the wrapper to position: relative, and position the text with position: absolute. The wrapper becomes the overlay’s positioning context. Because absolute positioning removes the text from normal flow, give the wrapper or image a dependable size and constrain the overlay so it remains readable on small screens.

1. Build the basic overlay

This pattern uses a semantic image and real HTML text. The image is cropped to fill a fixed-height frame, while the title sits over a dark gradient at the bottom.

<div class="hero">
  <img
    class="hero__image"
    src="scene.jpg"
    alt="A hiker looking across a mountain valley"
    width="1600"
    height="900"
  >
  <h1 class="hero__title">Find your next trail</h1>
</div>
.hero {
  position: relative;
  height: 24rem;
  overflow: hidden;
  background: #263238;
}

.hero__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero__title {
  position: absolute;
  inset: auto 0 0;
  margin: 0;
  padding: 3rem 1.5rem 1.5rem;
  color: white;
  background: linear-gradient(transparent, rgb(0 0 0 / 75%));
  font-size: clamp(1.75rem, 5vw, 3.5rem);
  line-height: 1.1;
}

The inset shorthand here places the title at the bottom and stretches it across the wrapper. You can use top, right, bottom, and left individually when that is easier to read. object-fit: cover fills the frame and crops excess image area; object-position changes which part remains visible.

2. Choose an image element or a CSS background

Use When Example
<img> The image conveys information, needs alternative text, or should retain image semantics. Article card, product image, informative hero photograph.
background-image The image is decorative and the page’s meaning is carried by its HTML text. Decorative banner texture or atmospheric hero art.

A CSS background has no built-in text alternative. If the image itself conveys information, use an <img> and provide an appropriate alt. Use alt="" when an image is purely decorative and the surrounding content already supplies the needed meaning.

Semantic image with a fixed crop

.card {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #263238;
}

.card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 65% center;
}

.card__label {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  max-width: calc(100% - 2rem);
  margin: 0;
  color: white;
}

Decorative background with a gradient

.hero--decorative {
  position: relative;
  min-height: 24rem;
  display: grid;
  align-items: end;
  padding: clamp(1rem, 5vw, 3rem);
  color: white;
  background-color: #263238;
  background-image:
    linear-gradient(0deg, rgb(0 0 0 / 75%), transparent 70%),
    url("scene.jpg");
  background-position: center, center;
  background-size: cover;
}

.hero--decorative h1 {
  max-width: 18ch;
  margin: 0;
}

This background example uses normal grid layout to place its text, so the text contributes to the block’s size. If you instead absolutely position the text, ensure the container has a minimum height or aspect ratio.

3. Position and size the overlay

Set position: relative on the element that should define the offsets. Without it, an absolutely positioned title may be placed relative to a different positioned ancestor or the initial containing block. Absolute elements do not contribute to the wrapper’s normal-flow size, so a wrapper containing only an absolutely positioned overlay can collapse unless the image, explicit dimensions, aspect ratio, or other in-flow content gives it size.

  • Bottom caption: use left: 0; right: 0; bottom: 0 and a gradient or opaque panel.
  • Corner badge: use a single corner such as top: 1rem; right: 1rem.
  • Centered text: use inset: 50% auto auto 50% plus transform: translate(-50%, -50%), or use grid/flex alignment if the text should remain in flow.
  • Text beside an image: use regular grid or flexbox; absolute positioning is for overlap, not ordinary side-by-side layout.

For long or translated headings, set a maximum width, allow wrapping, and use responsive padding and type sizes. Check narrow viewports, long strings, localization, text zoom, and image crops. A fixed overlay can cover important parts of the image or other content as text grows.

4. Keep overlay text accessible and legible

  • Keep important words as selectable HTML text; do not bake them into the image.
  • Check contrast against the actual image area behind the letters. WCAG guidance cited by MDN calls for a contrast ratio of 4.5:1 for ordinary text and 3:1 for large text.
  • A gradient or solid text panel can improve contrast, but verify the text against the resulting background rather than assuming the overlay is sufficient.
  • Set a fallback background color so text remains visible if the image fails to load.
  • At increased text zoom, ensure positioned content does not obscure other content or overflow its container.

For a photo whose tones vary, a solid or semi-opaque panel behind the copy is often more dependable than relying on a gradient alone.

5. Troubleshoot common problems

Symptom Likely cause Fix
Text appears in a page corner instead of over the image. The intended wrapper is not a positioning context. Add position: relative to the wrapper and check for another positioned ancestor.
The wrapper has no height or collapses. Its only child is absolutely positioned, so that child contributes no normal-flow size. Keep the image in flow, or set a height, min-height, or aspect-ratio on the wrapper.
The image is stretched or distorted. Width and height are forced without a crop strategy. Use object-fit: cover for an image element, or background-size: cover for a background; tune the position.
The subject is cropped out. The default centered crop does not suit the frame. Adjust object-position or background-position, for example object-position: 70% center.
Text is hard to read on some images. Contrast varies across the image. Measure contrast where the text sits; strengthen the gradient or add a solid backing panel.
Text overflows on mobile or with larger type. The overlay has fixed dimensions, narrow padding, or no wrapping constraints. Allow wrapping, cap width with a responsive maximum, reduce spacing at narrow widths, and inspect zoomed text.
Screen readers miss the image’s meaning. A meaningful image was implemented as a CSS background. Use an <img> with suitable alternative text.

6. Check the result across viewports

  1. Inspect the intended desktop and mobile widths.
  2. Check the image crop and the text’s contrast at each size.
  3. Test the longest expected heading and translated copy.
  4. Increase text size or zoom and confirm the title remains available and does not cover neighboring content.
  5. Disable or block the image and confirm the fallback color still makes the text usable.

A screenshot can help you compare layout states while tuning CSS. Capture the same page at your target viewport after each meaningful change, then inspect wrapping, crop, and contrast in the actual composition.

7. Or skip the browser setup

If you need screenshots of the finished page for review, documentation, or an automated workflow, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; its options include viewport and device presets, full-page capture, custom CSS and JavaScript, and waiting for a selector or network idle. See the ScreenshotNeo API docs.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie and consent banners are accepted and removed, along with known newsletter popups and chat widgets, before the shot; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server gives AI agents tools to take screenshots, inspect page info, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month with no card.

8. FAQ

Can I select the overlay text?

Yes. Keep it as HTML text above the image in the same wrapper; avoid embedding important copy in an image file.

Should I use z-index?

Only if the image or another overlapping element paints above the text. First establish the wrapper and overlay positioning; then set a suitable z-index if the stacking order requires it.

Can the text be centered without absolute positioning?

Yes. Use CSS grid or flexbox alignment when the text should participate in the wrapper’s layout and sizing.

What should I use if every card has different image dimensions?

Use a consistent frame such as aspect-ratio, then use object-fit: cover and adjust the crop per image when needed.

References