CSS3 & Responsive Design

Overflow, Clipping & Visibility

40 min Lesson 17 of 60

Understanding Overflow in CSS

When content inside an element exceeds the element's defined dimensions, the browser must decide what to do with the extra content. The overflow property controls this behavior and is one of the most important layout properties in CSS. Without proper overflow management, your layouts can break, content can overlap other elements, and scrollbars can appear in unexpected places.

Every element in CSS creates a rectangular box. When you set explicit width and height values on that box, you are telling the browser the maximum space available for the content. If the content -- whether text, images, or nested elements -- needs more space than the box provides, overflow occurs. Understanding how to handle this overflow is crucial for building robust, predictable layouts.

The overflow Property Values

The overflow property accepts several values, each producing different behavior when content exceeds its container:

  • visible -- The default value. Content that overflows the element's box is rendered outside the box and remains visible. No clipping occurs, and no scrollbars appear.
  • hidden -- Content that overflows the element's box is clipped. The overflowing content is invisible, and no scrollbar is provided to access it.
  • scroll -- Content that overflows is clipped, but the browser always displays scrollbars -- even if the content fits within the element. This ensures consistent visual appearance.
  • auto -- The browser decides. If content overflows, scrollbars appear. If content fits, no scrollbars are shown. This is the most commonly used value for scrollable containers.
  • clip -- Similar to hidden, but it also prevents programmatic scrolling via JavaScript. The element cannot be scrolled at all, unlike hidden where scrollTo() still works.

Example: Comparing overflow Values

/* Content spills out of the box (default) */
.overflow-visible {
    width: 200px;
    height: 100px;
    overflow: visible;
    border: 2px solid #333;
}

/* Content is cut off at the box boundary */
.overflow-hidden {
    width: 200px;
    height: 100px;
    overflow: hidden;
    border: 2px solid #333;
}

/* Scrollbars always appear, even if not needed */
.overflow-scroll {
    width: 200px;
    height: 100px;
    overflow: scroll;
    border: 2px solid #333;
}

/* Scrollbars appear only when content overflows */
.overflow-auto {
    width: 200px;
    height: 100px;
    overflow: auto;
    border: 2px solid #333;
}

/* Content clipped; no scrolling at all */
.overflow-clip {
    width: 200px;
    height: 100px;
    overflow: clip;
    border: 2px solid #333;
}
Note: The overflow: clip value is a newer addition to CSS. Unlike hidden, it does not create a new scroll container, which means JavaScript's element.scrollTo() or element.scrollTop will have no effect. Use clip when you want absolutely no scrolling behavior.

Controlling Overflow Per Axis: overflow-x and overflow-y

CSS also provides overflow-x and overflow-y to control overflow on each axis independently. This is extremely useful when you want, for example, horizontal scrolling without vertical scrolling, or you want to clip content vertically but allow horizontal overflow.

Example: Independent Axis Overflow

/* Horizontal scrolling only */
.horizontal-scroll {
    width: 300px;
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
}

/* Vertical scrolling only */
.vertical-scroll {
    height: 200px;
    overflow-x: hidden;
    overflow-y: auto;
}

/* A code block that scrolls horizontally but not vertically */
.code-container {
    max-width: 100%;
    overflow-x: auto;
    overflow-y: visible;
    padding: 16px;
    background: #f4f4f4;
    border-radius: 8px;
}
Warning: If you set one axis to visible and the other to hidden, scroll, or auto, the browser will internally treat the visible axis as auto. This is because a scrollable element must clip content on both axes to function properly. This is a common source of confusion when mixing overflow values.

Text Overflow and Ellipsis

The text-overflow property controls how overflowing inline text is signaled to the user. The most common use case is truncating long text with an ellipsis (...). However, text-overflow does not work alone -- it requires a specific combination of properties to function correctly.

For text truncation to work, you need all three of these properties together:

  1. overflow: hidden -- Clips the overflowing text.
  2. white-space: nowrap -- Prevents the text from wrapping to a new line.
  3. text-overflow: ellipsis -- Replaces the clipped text with an ellipsis character.

Example: Single-Line Text Truncation

/* The classic ellipsis truncation pattern */
.truncate {
    width: 250px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Usage in HTML:
   <p class="truncate">
       This is a very long paragraph that will be truncated
       with an ellipsis when it overflows the container.
   </p>
*/

For multi-line truncation, CSS provides the -webkit-line-clamp property, which works in combination with a flex-based display mode. This allows you to show a specific number of lines before truncating with an ellipsis.

Example: Multi-Line Text Truncation

/* Truncate after 3 lines with an ellipsis */
.line-clamp-3 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Truncate after 2 lines */
.line-clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Usage in HTML:
   <div class="line-clamp-3">
       <p>This is a long text that spans multiple lines.
       It will be truncated after three lines and an ellipsis
       will appear at the end of the third line to indicate
       that there is more content that is hidden from view.</p>
   </div>
*/
Tip: Despite the -webkit- prefix, -webkit-line-clamp is supported by all modern browsers including Firefox and Safari. It is the only reliable way to achieve multi-line text truncation with CSS alone.

Creating Scrollable Containers

Scrollable containers are essential for building modern web interfaces -- chat windows, sidebars, data tables, and image galleries all rely on scrollable areas. The key is setting a fixed dimension and using overflow: auto so scrollbars appear only when needed.

Example: Building Scrollable Containers

/* A scrollable sidebar */
.sidebar {
    width: 280px;
    height: 100vh;
    overflow-y: auto;
    padding: 20px;
    background-color: #f8f9fa;
    position: sticky;
    top: 0;
}

/* A scrollable chat window */
.chat-messages {
    height: 400px;
    overflow-y: auto;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* A horizontally scrollable image gallery */
.gallery {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding: 16px 0;
    scroll-snap-type: x mandatory;
}

.gallery img {
    flex-shrink: 0;
    width: 300px;
    height: 200px;
    object-fit: cover;
    border-radius: 8px;
    scroll-snap-align: start;
}

/* A scrollable data table wrapper */
.table-wrapper {
    max-width: 100%;
    overflow-x: auto;
    border: 1px solid #dee2e6;
    border-radius: 8px;
}

.table-wrapper table {
    min-width: 800px;
    width: 100%;
    border-collapse: collapse;
}

Custom Scrollbar Styling

Default browser scrollbars can clash with your design. CSS provides two approaches to customize scrollbars: the WebKit-specific pseudo-elements and the standards-based scrollbar-width and scrollbar-color properties.

WebKit Scrollbar Pseudo-Elements

WebKit-based browsers (Chrome, Edge, Safari) support several pseudo-elements for granular scrollbar control:

  • ::-webkit-scrollbar -- The entire scrollbar.
  • ::-webkit-scrollbar-track -- The background track of the scrollbar.
  • ::-webkit-scrollbar-thumb -- The draggable part of the scrollbar.
  • ::-webkit-scrollbar-button -- The up/down or left/right arrow buttons.
  • ::-webkit-scrollbar-corner -- The corner where horizontal and vertical scrollbars meet.

Example: Custom Scrollbar with WebKit Pseudo-Elements

/* Custom scrollbar for a specific container */
.custom-scroll::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}

.custom-scroll::-webkit-scrollbar-track {
    background: #f1f1f1;
    border-radius: 4px;
}

.custom-scroll::-webkit-scrollbar-thumb {
    background: #888;
    border-radius: 4px;
}

.custom-scroll::-webkit-scrollbar-thumb:hover {
    background: #555;
}

/* Hide scrollbar but keep scrolling functional */
.hidden-scrollbar::-webkit-scrollbar {
    display: none;
}

.hidden-scrollbar {
    -ms-overflow-style: none;   /* IE and Edge */
    scrollbar-width: none;      /* Firefox */
}

Standards-Based Scrollbar Styling

The CSS Scrollbars Specification provides two properties that work across Firefox and newer versions of Chrome:

Example: Standards-Based Scrollbar Styling

/* Thin scrollbar with custom colors */
.styled-scrollbar {
    scrollbar-width: thin;           /* auto | thin | none */
    scrollbar-color: #888 #f1f1f1;  /* thumb-color track-color */
}

/* Hide the scrollbar entirely (Firefox) */
.no-scrollbar {
    scrollbar-width: none;
}

/* Combine both approaches for cross-browser support */
.cross-browser-scrollbar {
    /* Standards-based (Firefox) */
    scrollbar-width: thin;
    scrollbar-color: #6366f1 #e5e7eb;
}

.cross-browser-scrollbar::-webkit-scrollbar {
    width: 6px;
}

.cross-browser-scrollbar::-webkit-scrollbar-track {
    background: #e5e7eb;
}

.cross-browser-scrollbar::-webkit-scrollbar-thumb {
    background: #6366f1;
    border-radius: 3px;
}
Tip: Always provide both WebKit pseudo-elements and the standards-based properties for maximum browser compatibility. Test your custom scrollbars in Firefox and Chrome to ensure consistency.

Visibility: visible, hidden, and collapse

The visibility property controls whether an element is visible on the page. Unlike display: none, a hidden element still occupies space in the layout -- it is simply invisible.

  • visibility: visible -- The default value. The element is visible.
  • visibility: hidden -- The element is invisible but still takes up its normal space in the layout. Other elements are not repositioned.
  • visibility: collapse -- For table rows, columns, and groups, it removes the row/column and the space it occupied. On other elements, it behaves like hidden.

visibility: hidden vs display: none

This is one of the most important distinctions in CSS:

Example: visibility: hidden vs display: none

<style>
    .container {
        display: flex;
        gap: 10px;
    }
    .box {
        width: 100px;
        height: 100px;
        background: #6366f1;
    }
    /* Element is invisible but still occupies 100x100px space */
    .box-hidden {
        visibility: hidden;
    }
    /* Element is completely removed from the layout */
    .box-none {
        display: none;
    }
</style>

<div class="container">
    <div class="box">1</div>
    <div class="box box-hidden">2 (hidden)</div>
    <div class="box">3</div>
</div>
<!-- Box 3 stays in its position because Box 2 still occupies space -->

<div class="container">
    <div class="box">1</div>
    <div class="box box-none">2 (none)</div>
    <div class="box">3</div>
</div>
<!-- Box 3 moves up next to Box 1 because Box 2 is completely removed -->

Key differences between the two:

  • Layout impact: visibility: hidden preserves the element's space; display: none removes it entirely.
  • Accessibility: Both are hidden from screen readers, but visibility: hidden with aria-hidden="false" can be complex.
  • Transitions: You can transition visibility (it switches between visible and hidden at the midpoint), but you cannot transition display.
  • Events: visibility: hidden elements do not receive click events. display: none elements do not exist in the layout at all.
  • Performance: display: none triggers a full layout recalculation when toggled. visibility: hidden only requires a repaint.

The opacity Property

The opacity property sets the transparency level of an element. It ranges from 0 (completely transparent) to 1 (fully opaque). Unlike visibility: hidden, an element with opacity: 0 still receives click events and occupies space.

Example: Using opacity for Fade Effects

/* Fully transparent -- element is invisible but interactive */
.transparent {
    opacity: 0;
}

/* Semi-transparent -- see-through effect */
.semi-transparent {
    opacity: 0.5;
}

/* Fade-in effect on hover */
.fade-card {
    opacity: 0.7;
    transition: opacity 0.3s ease;
}

.fade-card:hover {
    opacity: 1;
}

/* Disabled state with reduced opacity */
.button-disabled {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;  /* Prevents clicking */
}

/* Image overlay with opacity */
.overlay {
    position: absolute;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.6);
    opacity: 0;
    transition: opacity 0.3s ease;
}

.card:hover .overlay {
    opacity: 1;
}
Warning: Opacity applies to the entire element and all its children. If you set opacity: 0.5 on a parent, the children cannot be more opaque than 50%. If you only need a semi-transparent background, use rgba() or hsla() for the background-color instead.

clip-path: Clipping Elements into Shapes

The clip-path property allows you to define a visible region for an element. Anything outside the defined shape is clipped and becomes invisible. This is a powerful property for creating non-rectangular layouts, image effects, and creative designs without extra HTML or images.

Basic Shapes

CSS provides four built-in shape functions for clip-path:

  • circle() -- Creates a circular clipping region. Syntax: circle(radius at center-x center-y).
  • ellipse() -- Creates an elliptical clipping region. Syntax: ellipse(rx ry at center-x center-y).
  • polygon() -- Creates a clipping region from a series of coordinate points. Each point is an x y pair.
  • inset() -- Creates a rectangular clipping region with optional rounded corners. Syntax: inset(top right bottom left round border-radius).

Example: clip-path Basic Shapes

/* Circle -- 50% radius centered by default */
.clip-circle {
    clip-path: circle(50%);
}

/* Circle -- custom position */
.clip-circle-offset {
    clip-path: circle(40% at 30% 50%);
}

/* Ellipse */
.clip-ellipse {
    clip-path: ellipse(50% 35% at 50% 50%);
}

/* Triangle using polygon */
.clip-triangle {
    clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}

/* Pentagon using polygon */
.clip-pentagon {
    clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
}

/* Diagonal cut */
.clip-diagonal {
    clip-path: polygon(0 0, 100% 0, 100% 80%, 0 100%);
}

/* Inset with rounded corners */
.clip-inset {
    clip-path: inset(10px 20px 10px 20px round 15px);
}

/* Animated clip-path on hover */
.clip-animated {
    clip-path: circle(0% at 50% 50%);
    transition: clip-path 0.6s ease;
}

.clip-animated:hover {
    clip-path: circle(75% at 50% 50%);
}
Tip: The clip-path property can be animated with CSS transitions, enabling creative reveal effects. When transitioning between shapes, ensure both the starting and ending shapes use the same function (both circle() or both polygon() with the same number of points).

mask and mask-image

The mask and mask-image properties provide another way to clip elements, but with more flexibility than clip-path. Masks use the alpha channel (transparency) of an image to determine which parts of the element are visible. Black areas of the mask hide the element, white areas show it, and gray areas create semi-transparent effects.

Example: Using mask-image

/* Gradient mask -- fades out at the bottom */
.fade-bottom {
    -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
    mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
}

/* Radial gradient mask -- spotlight effect */
.spotlight {
    -webkit-mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);
    mask-image: radial-gradient(circle at 50% 50%, black 30%, transparent 70%);
}

/* SVG mask from a URL */
.svg-mask {
    -webkit-mask-image: url('mask-shape.svg');
    mask-image: url('mask-shape.svg');
    -webkit-mask-size: cover;
    mask-size: cover;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}

/* Text fade-out for long content */
.text-fade {
    max-height: 200px;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
    mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
}
Note: Always include the -webkit- prefix for mask-image properties. Safari still requires the prefixed version, and Chrome supports both. Firefox uses the unprefixed version.

content-visibility for Rendering Performance

The content-visibility property is a relatively new CSS feature designed to improve rendering performance. It allows the browser to skip the rendering of off-screen elements until they are needed. This can dramatically speed up initial page load times on pages with a lot of content.

  • content-visibility: visible -- The default. Content is rendered normally.
  • content-visibility: hidden -- Content is not rendered and not accessible. Similar to display: none but preserves rendering state for faster re-rendering.
  • content-visibility: auto -- The browser automatically skips rendering for off-screen elements and renders them when they scroll into view.

Example: content-visibility for Performance

/* Each section of the page can use content-visibility: auto */
.page-section {
    content-visibility: auto;
    contain-intrinsic-size: 0 500px;  /* Estimated height for layout */
}

/* Blog post cards in a long list */
.blog-card {
    content-visibility: auto;
    contain-intrinsic-size: 0 350px;
}

/* Tab panels that are not visible */
.tab-panel[hidden] {
    content-visibility: hidden;
}

/* Combined with contain for maximum performance */
.performance-optimized {
    content-visibility: auto;
    contain-intrinsic-size: auto 300px;
    contain: layout style paint;
}
Warning: When using content-visibility: auto, always provide a contain-intrinsic-size value. Without it, the browser may treat the element as having zero height when off-screen, causing the scrollbar to jump erratically as the user scrolls and elements render in.

Practical Comparison: Hiding Elements

CSS provides many ways to hide elements, each with different implications for layout, accessibility, performance, and interactivity. Here is a summary of all the approaches:

  • display: none -- Completely removes the element from the layout. Not accessible to screen readers. Cannot be transitioned.
  • visibility: hidden -- Hides the element but preserves its space. Not accessible. Can be transitioned (snaps at midpoint).
  • opacity: 0 -- Makes the element fully transparent. Still occupies space and still receives pointer events. Can be smoothly transitioned.
  • clip-path: circle(0) -- Clips the element to nothing. Still occupies space. Can be smoothly transitioned.
  • transform: scale(0) -- Shrinks the element to a point. Still occupies original space. Can be smoothly transitioned.
  • content-visibility: hidden -- Hides content and preserves rendering state. Not accessible.
  • position: absolute; left: -9999px -- Moves the element off-screen. Still accessible to screen readers. Used for visually-hidden utility classes.

Exercise 1: Scrollable Card with Custom Scrollbar

Create a card component with a fixed height of 300px and a width of 400px. Inside the card, add enough text content to cause overflow. Apply overflow-y: auto to make it scrollable. Then style the scrollbar using both WebKit pseudo-elements and the standards-based scrollbar-width and scrollbar-color properties. The scrollbar thumb should be a rounded blue (#3b82f6) bar on a light gray (#f3f4f6) track. The scrollbar should be 6px wide. Test in both Chrome and Firefox to verify cross-browser compatibility.

Exercise 2: Image Reveal with clip-path

Create an image card that uses clip-path to reveal itself on hover. Start with clip-path: circle(0% at 50% 50%) so the image is completely hidden. On hover, transition to clip-path: circle(75% at 50% 50%) with a smooth 0.5 second ease transition. Add a text overlay on top of the image that uses opacity to fade in when hovered. The overlay should have a semi-transparent black background using rgba(0, 0, 0, 0.5) and white text. Also add a mask-image gradient fade effect on a separate paragraph below the card so that the text fades out at the bottom.

Exercise 3: Truncated Blog Preview

Build a blog post preview card that shows a title, a publication date, and a content excerpt. The title should be truncated to a single line with an ellipsis using overflow: hidden, white-space: nowrap, and text-overflow: ellipsis. The content excerpt should be clamped to exactly 3 lines using -webkit-line-clamp. Below the excerpt, add a "Read more" link. Style the entire card with a subtle border and padding, and ensure the card has content-visibility: auto with an appropriate contain-intrinsic-size for performance optimization.