035: Borders, Shadows, Opacity, Images, and Filters
Learning outcomes
By the end, you can use borders and outlines to establish structure and communicate focus; create restrained box and text shadows; tell the difference between element opacity and alpha colors; size and crop images with object-fit; use CSS filters and backdrop filters responsibly; and debug visual effects that introduce stacking or performance problems.
Prerequisites and retrieval
Before starting, retrieve backgrounds from 021, the box model from 023, positioning and stacking from 026, and responsive design from 030. Those earlier ideas explain much of what happens when these visual effects meet real layouts.
Borders
A border is part of the box's visible boundary, so it is useful for separating regions and giving a component a dependable edge.
.card {
border: 1px solid #cbd5e1;
}
Longhands:
.card {
border-width: 1px;
border-style: solid;
border-color: #cbd5e1;
}
The longhand properties are useful when different sides need different values or when a component's styles are assembled from several rules.
Logical side:
.notice {
border-inline-start: 4px solid #2563eb;
}
border-inline-start follows the document's inline direction. That makes the rule adapt better to right-to-left writing modes than a hard-coded border-left would.
Radius:
.card {
border-radius: 0.75rem;
}
border-radius rounds the corners of the border box. A large radius can produce a pill when the box is sufficiently short:
.tag {
border-radius: 999px;
}
Outline versus border
Use an outline when the visual needs to sit outside the box without changing its dimensions. This is especially useful for keyboard focus:
.button:focus-visible {
outline: 3px solid #f59e0b;
outline-offset: 3px;
}
Outlines do not usually consume box-model space, which makes them well suited to focus indicators. Do not replace one with a subtle border color if that border is difficult to perceive. A keyboard user needs a clear indication of which control is active.
Box shadows
The basic syntax is:
.card {
box-shadow:
0 1px 2px rgb(15 23 42 / 0.08),
0 8px 24px rgb(15 23 42 / 0.08);
}
Think of each shadow in terms of its parameters:
x-offset y-offset blur spread color.
Multiple shadows can build a softer, more controlled elevation than one very dark shadow. An inset shadow is painted inside the box instead:
.input {
box-shadow: inset 0 1px 2px rgb(15 23 42 / 0.08);
}
Shadows should communicate an elevation or focus hierarchy. Once they become the main visual signal, they usually add noise rather than useful structure.
Worked example: accessible card elevation
This card uses a border for its basic boundary, a modest shadow for resting elevation, and a stronger shadow on hover. Focus is communicated with an outline rather than relying on the hover treatment:
.card {
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
box-shadow: 0 1px 3px rgb(15 23 42 / 0.08);
background: white;
}
.card:hover {
box-shadow: 0 8px 24px rgb(15 23 42 / 0.12);
}
.card:focus-within {
outline: 3px solid #2563eb;
outline-offset: 3px;
}
The border still defines the card if shadows disappear in a high-contrast environment. That is the useful accessibility pattern: the effect can reinforce the structure, but it should not be the only way the structure is communicated.
Text shadow
Text shadows can help separate text from a busy image, but they are not a substitute for sufficient contrast:
.hero h1 {
text-shadow: 0 2px 8px rgb(0 0 0 / 0.35);
}
Do not use a shadow to rescue unreadable image and text contrast. Add a background overlay or choose a better image so the text remains readable without depending on a fragile effect.
Opacity versus alpha
These two techniques can look similar while affecting very different parts of the rendering tree. opacity applies to the entire element and its descendants:
.disabled-card {
opacity: 0.55;
}
An alpha color can make just one painted layer translucent. The content inside the element remains unaffected:
.overlay {
background: rgb(15 23 42 / 0.55);
}
opacity also creates a stacking context when below 1 and affects descendants. This combination can produce surprising layering, especially when a child has a z-index that you expected to compare with elements outside its parent.
Images: sizing and cropping
Start with a responsive default for images. It keeps the image inside its available inline space while preserving its intrinsic aspect ratio:
img {
max-inline-size: 100%;
block-size: auto;
}
For a component that needs a predictable frame, define the frame's dimensions and then choose how the image fills it:
.product-image {
inline-size: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
object-position: center;
}
cover fills the content box and crops whatever does not fit. contain keeps the entire image visible, but it may leave empty space in the frame. Choose between them based on whether the frame or the complete image is more important.
Worked example: avatars
An avatar usually needs a square frame and a crop that fills that frame:
.avatar {
inline-size: 3rem;
aspect-ratio: 1;
object-fit: cover;
border-radius: 50%;
}
CSS filters
Filters change the pixels produced by an element for presentation purposes. For example, a thumbnail can be shown in grayscale:
.thumbnail {
filter: grayscale(100%);
}
Several filter functions can be composed in one declaration:
filter:
blur(2px)
brightness(0.9)
contrast(1.1)
saturate(1.2);
Use filters for presentation. Do not blur or redact sensitive information with CSS and assume the data is protected. The original content still exists in the document or image data and may remain available to users or tools.
Backdrop filters
Unlike filter, backdrop-filter processes pixels behind the element. A translucent panel can therefore blur the page or image underneath it:
.glass-panel {
background: rgb(255 255 255 / 0.65);
backdrop-filter: blur(12px);
}
The effect can be attractive, but it can also be expensive and make text harder to read. Always provide a sufficiently opaque fallback surface for browsers that do not support the property or for situations where the effect is not rendered as expected.
.glass-panel {
background: rgb(255 255 255 / 0.92);
}
@supports (backdrop-filter: blur(12px)) {
.glass-panel {
background: rgb(255 255 255 / 0.7);
backdrop-filter: blur(12px);
}
}
Worked example: image card overlay
The semantic content stays in HTML, while the gradient is a decorative layer positioned over the image:
<article class="story-card">
<img src="city.jpg" alt="Evening city skyline" width="1200" height="800">
<div class="story-card__content">
<h2>City guide</h2>
<a href="/city">Read guide</a>
</div>
</article>
.story-card {
position: relative;
overflow: clip;
border-radius: 1rem;
}
.story-card img {
inline-size: 100%;
block-size: 100%;
min-block-size: 20rem;
object-fit: cover;
}
.story-card::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
to top,
rgb(15 23 42 / 0.9),
transparent 65%
);
}
.story-card__content {
position: absolute;
inset-inline: 1.25rem;
inset-block-end: 1.25rem;
z-index: 1;
color: white;
}
The gradient is decorative. The meaningful title and link remain real HTML, so the content retains its semantic and interactive behavior. Positioning is used here for a deliberate overlay, not as a replacement for normal document layout.
Visual-effect performance
Large blurred shadows, filters, backdrop filters, and effects applied to huge moving regions can all be expensive. The actual cost depends on the browser, device, effect, and area being repainted, so measure before optimizing rather than guessing from the property name alone.
General principles:
- keep blur regions bounded;
- avoid animating blur on large surfaces without testing;
- avoid dozens of layered shadows;
- prefer static effects when motion adds little value;
- test lower-powered devices.
Deep dive: paint, clipping, and image composition
Borders, shadows, images, filters, and opacity mostly affect painting. Some of these properties can also change containing blocks or stacking behavior, so a visual problem is not always just a matter of color or decoration.
Multiple backgrounds
CSS can paint multiple background layers. This is a useful way to place a contrast overlay above an image without adding another element:
.hero {
background:
linear-gradient(
90deg,
rgb(2 6 23 / 0.82),
rgb(2 6 23 / 0.2)
),
url("/images/hero.webp")
center / cover no-repeat;
}
The first listed background is painted on top of the later layers. Keep the contrast readable even when the underlying image changes; an overlay that works for one photo may be insufficient for another.
border-radius and clipping
A rounded border does not automatically mean every descendant will be clipped exactly as you expect. If content needs to stay inside the rounded shape, make the clipping decision explicit:
.card {
border-radius: 1rem;
overflow: clip;
}
Use clipping deliberately. Avoid adding overflow: hidden only to make corners look right if the component also needs visible focus rings, sticky descendants, or popovers. Clipping may solve the corner appearance while creating a new interaction problem.
Replaced elements and object-fit
Images are replaced elements. object-fit controls how their content is fitted inside the content box:
.avatar {
inline-size: 6rem;
aspect-ratio: 1;
object-fit: cover;
object-position: 50% 30%;
}
object-fit controls how replaced content such as an image fits its content box. It does not resize the surrounding layout box by itself. If the frame is the wrong size, inspect the dimensions and aspect ratio rather than trying another crop mode.
Alpha versus element opacity
The choice is easiest to see when the element contains text:
/* only the background is translucent */
.notice {
background: rgb(37 99 235 / 0.12);
}
/* the entire element subtree becomes translucent */
.disabled-preview {
opacity: 0.5;
}
Using opacity on a container affects text and descendants too. Prefer alpha colors when only one paint layer should be translucent. This also reduces the chance that an unintended stacking context will complicate the component.
Deep dive: filter cost and stacking behavior
Filters are useful for small, targeted presentation changes:
.thumbnail:hover img {
filter: saturate(1.08) contrast(1.04);
}
Large blurred regions and backdrop-filter can be expensive because the browser may need extra offscreen rendering before it can composite the result:
.frosted-toolbar {
background: rgb(255 255 255 / 0.7);
backdrop-filter: blur(12px);
}
Keep these effects on limited areas, measure them on lower-powered devices, and provide a readable base background when the effect is unavailable. If an effect is animated, test the animation rather than assuming the static version represents its cost.
Properties such as filter, opacity < 1, and transforms can create stacking contexts. If an overlay unexpectedly appears behind a neighboring component, inspect the stacking contexts before increasing z-index. A larger z-index cannot make an element escape the stacking context created by its ancestor.
Visual debugging checklist
When an image or effect looks wrong, inspect the problem in this order:
- the element's actual box dimensions;
- intrinsic image dimensions;
aspect-ratio;object-fitandobject-position;- overflow/clipping;
- background layer order;
- stacking contexts;
- contrast after filters/overlays;
- paint cost in DevTools.
Do not solve a geometry problem by piling on visual effects. First establish whether the box, source image, crop, or containing context is wrong; only then adjust the visual treatment.
Common mistakes
- Removing outline and replacing it with a barely visible shadow.
- Using
opacity: 0to hide focusable content. - Using CSS blur to “secure” private data.
- Cropping an essential image so aggressively that meaning is lost.
- Loading a huge background image for a tiny card.
- Overusing glass/blur effects that reduce contrast.
- Creating stacking contexts accidentally with opacity/filter and then fighting
z-index.
Practice set
- Build three elevation levels with one border plus restrained shadows.
- Compare
opacity: .5with an alpha background color. - Create a 1:1 avatar and 16:9 thumbnail using
aspect-ratioandobject-fit. - Add a filter only on hover, then remove it for reduced-motion if transition is used.
- Build a glass panel with an opaque fallback.
- Inspect stacking contexts created by
opacityandfilter.
Recap
Borders describe boundaries, outlines communicate focus, shadows suggest depth, opacity affects the entire box tree, and filters alter rendered pixels. Treat visual effects as supporting signals, not as the only source of meaning. The underlying structure and content should still work when an effect is unavailable or deliberately reduced.
Official references
-
MDN:
object-fit— https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit -
MDN:
filter— https://developer.mozilla.org/en-US/docs/Web/CSS/filter -
MDN: Borders — https://developer.mozilla.org/en-US/docs/Web/CSS/border
-
MDN: Box shadow — https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow
-
MDN: Opacity — https://developer.mozilla.org/en-US/docs/Web/CSS/opacity
-
MDN:
object-fit— https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit -
MDN: Filter — https://developer.mozilla.org/en-US/docs/Web/CSS/filter
-
MDN: Backdrop filter — https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter
