Handling overflow: try fallbacks and conditional hiding

When using CSS anchor positioning, an important consideration is ensuring that anchor-positioned elements always appear in a convenient place for the user to interact with them, if at all possible, regardless of where the anchor is positioned. For example, when you scroll the page, anchors and their associated positioned elements will move toward the edge of the viewport. When a positioned element starts to overflow the viewport, you will want to change its position to put it back on the screen again, for example on the opposite side of the anchor.

Alternatively, in some situations it may be preferable to just hide overflowing positioned elements — for example, if their anchors are off-screen their content might not make sense.

This guide explains how to use CSS anchor positioning mechanisms to manage these issues — position-try fallback options and conditional hiding. Position-try fallback options provide alternative positions for the browser to try placing positioned elements in as they start to overflow, to keep them on-screen. Conditional hiding allows conditions to be specified under which the anchor or a positioned element will be hidden.

Note: For information on the basic fundamentals of CSS anchor positioning, see Using CSS anchor positioning.

Feature summary

If a tooltip is fixed to the top-right of a UI element, when the user scrolls the content so that the UI feature is in the top-right corner of the viewport, that UI feature's tooltip will have scrolled off the screen. CSS anchor positioning solves such problems. The module's position-try-fallbacks property specifies one or more alternative position-try fallback options for the browser to try to prevent the positioned element from overflowing.

Position-try fallback options can be specified using:

In addition, the position-try-order property allows you to specify various options that result in an available position try option being set in preference to the element's initial positioning. For example, you might want to initially display the element in a space that has more available height or width.

The shorthand property position-try can be used to specify position-try-order and position-try-fallbacks values in a single declaration.

In some situations, anchor-positioned content does not make sense if the anchor is off-screen, or vice-versa. For example, you might have an anchor containing a quiz question, and answers contained in associated positioned elements, and wish to show them both together or not at all. This can be achieved with conditional hiding, which is managed via the position-visibility property. This property takes various values that define conditions under which overflowing elements will be hidden.

Predefined fallback options

The predefined fallback option values of the position-try-fallbacks property (defined as <try-tactic>s in the spec) will "flip" the position of the anchor-positioned element across one or both axes if the element would otherwise overflow.

The element can be set to flip across the block axis (flip-block), the inline axis (flip-inline), or diagonally across an imaginary line drawn from a corner of the anchor through its center to its opposite corner (flip-start). These three values flip the element, mirroring its position on an opposite side for the first two values, and an adjacent side for flip-start. For example, if an element positioned 10px above its anchor starts to overflow at the top of the anchor, the flip-block value would flip the positioned element to be 10px below its anchor.

In this example, we include two <div> elements. The first one will be our anchor element, and the second one will be positioned relative to the anchor:

html
<div class="anchor">⚓︎</div>

<div class="infobox">
  <p>This is an information box.</p>
</div>

We style the <body> element to be larger than the viewport so that we can scroll the anchor and the positioned element around in the viewport, both horizontally and vertically:

css
body {
  width: 1500px;
  height: 500px;
}

For illustrative purposes, we absolutely position the anchor so that it appears near the center of the initial <body> rendering:

The anchor-positioned element is given fixed positioning and tethered to the anchor's top-left corner using a position-area. It is given position-try-fallbacks: flip-block, flip-inline; to provide it with some fallback options for moving the positioned element to stop it from overflowing when the anchor gets near the edge of the viewport.

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  position-area: top left;
  position-try-fallbacks: flip-block, flip-inline;
}

Note: When multiple position try fallback options are specified, they are separated by commas, and tried in the order they are specified.

Try scrolling the demo so that the anchor starts to get near the edges:

  • Move the anchor to the top of the viewport. The positioned element flips to the bottom-left of the anchor to avoid overflowing.
  • Move the anchor to the left of the viewport. The positioned element flips to the top-right of the anchor to avoid overflowing.

If you move the anchor towards the top-left corner of the viewport, you'll notice a problem — as the positioned element starts to overflow in the block and inline direction, it flips back to its default top-left position and overflows in both directions, which is not what we want.

This happens because we only gave the browser position options of flip-block or flip-inline. We didn't give it the option of trying both at the same time. The browser tries the fallback options, looking for one that causes the positioned element to be rendered completely inside the viewport or containing block. If it doesn't find one, it renders the positioned element in its originally-defined rendering position, with no position fallback options applied.

The next section demonstrates how to fix this issue.

Combining multiple values into one option

It is possible to put multiple predefined try fallback options or custom try option names into a single space-separated try fallback option value within the comma-separated position-try-fallbacks list. When trying to apply these fallback options, the browser will combine the individual effects into a single combined fallback option.

Let's use a combined try fallback option to fix the problem we found with the previous demo. The HTML and CSS in this demo are the same, except for the infobox positioning styles. In this case, it is given a third position try fallback option: flip-block flip-inline:

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  position-area: top left;
  position-try-fallbacks:
    flip-block,
    flip-inline,
    flip-block flip-inline;
}

This means that the browser will first try flip-block and then try flip-inline to avoid overflow. If those fallback options both fail, it will then try combining the two, flipping the element's position in the block and inline directions at the same time. Now when you scroll the anchor towards the top and left edges of the viewport, the positioned element will flip over to the bottom-right.

Using position-area try fallback options

The predefined <try-tactic> try fallback options are useful but limited, as they only allow positioned element placement to be flipped across axes. What if you had an anchor-positioned element positioned to the top left of its anchor, and wanted to change its position to directly below the anchor if it started to overflow?

To achieve this, you can use a position-area value as a position-try fallback option, including it in the position-try-fallbacks list. This automatically creates a try fallback option based on that position area. In effect, it is a shortcut for creating a custom position option that contains only that position-area property value.

The following example shows position-area position try fallback options in use. We use the same HTML and CSS, except for the infobox positioning. In this case, our position-try fallback options are position-area values — top, top-right, right, bottom-right, bottom, bottom-left, and left. The positioned element will be reasonably positioned no matter which viewport edge the anchor approaches. This verbose approach is more granular and flexible than the predefined values approach.

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  position-area: top left;
  position-try-fallbacks:
    top, top right, right,
    bottom right, bottom,
    bottom left, left;
}

Note: You can't add position-area try fallback options into a space-separated combined position option within a position-try fallback list.

Scroll the page and check out the effect of these position-try fallback options as the anchor nears the edge of the viewport:

Custom fallback options

To use custom position fallback options that aren't available via the above mechanisms, you can create your own with the @position-try at-rule. The syntax is:

@position-try --try-fallback-name {
  descriptor-list
}

The --try-fallback-name is a developer-defined name for the position try fallback option. This name can then be specified within the comma-separated list of try fallback options within the position-try-fallbacks property value. If multiple @position-try rules have the same name, the last one in the document order overrides the others. Avoid using the same name for your try fallback options and your anchor or custom property names; it doesn't invalidate the at-rule, but it will make your CSS very difficult to follow.

The descriptor-list defines the property values for that individual custom try fallback option, including how the positioned element should be placed and sized, and any margins. The limited list of property descriptors allowed includes:

The values you include in the at-rule get applied to the positioned element if the named custom-try fallback option gets applied. If any of the properties were previously set on the positioned element, those property values get overridden by the descriptor values. If the user scrolls, causing a different try fallback option or no try fallback option to be applied, the values from the previously-applied try fallback option are unset.

In this example, we set up and use several custom try fallback options. We use the same base HTML and CSS code as in the previous examples.

We start by defining four custom try fallback options using @position-try:

css
@position-try --custom-left {
  position-area: left;
  width: 100px;
  margin: 0 10px 0 0;
}

@position-try --custom-bottom {
  position-area: bottom;
  margin: 10px 0 0 0;
}

@position-try --custom-right {
  position-area: right;
  width: 100px;
  margin: 0 0 0 10px;
}

@position-try --custom-bottom-right {
  position-area: bottom right;
  margin: 10px 0 0 10px;
}

Once our custom try fallback options are created, we can include them in the position list by referencing their names:

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  position-area: top;
  width: 200px;
  margin: 0 0 10px 0;
  position-try-fallbacks:
    --custom-left, --custom-bottom,
    --custom-right, --custom-bottom-right;
}

Note that our default position is defined by position-area: top. When the infobox isn't overflowing the page in any direction, the infobox sits above the anchor, and the position-try fallback options set in the position-try-fallbacks property are ignored. Also, note that the infobox has a fixed width and bottom margin set. These values will change as different position-try fallback options are applied.

If the infobox starts to overflow, the browser tries the position options listed in the position-try-fallbacks property:

  • The browser first tries the --custom-left fallback position. This moves the infobox to the left of the anchor, adjusts the margin to suit, and also gives the infobox a different width.
  • Next, the browser tries the --custom-bottom position. This moves the infobox to the bottom of the anchor, and sets an appropriate margin. It doesn't include a width descriptor, so the infobox returns to its default width of 200px set by the width property.
  • The browser next tries the --custom-right position. This works much the same as the --custom-left position, with the same width descriptor value applied, but the position-area and margin values are mirrored to place the infobox appropriately to the right.
  • If none of the other fallbacks succeed in stopping the positioned element from overflowing, the browser tries the --custom-bottom-right position as a last resort. This works in much the same way as the other fallback options, but it places the positioned element to the bottom-right of the anchor.

If none of the fallbacks succeed in stopping the positioned element from overflowing, the position will revert to the initial position-area: top; value.

Note: When a position try fallback option is applied, its values will override the default values set on the positioned element. For example, the default width set on the positioned element is 200px, but when the --custom-right position try fallback option is applied, its width is set to 100px.

Scroll the page and check out the effect of these position-try fallback options as the anchor nears the edge of the viewport:

Using position-try-order

The position-try-order property has a slightly different focus to the rest of the position try functionality, in that it makes use of position try fallback options when the positioned element is first displayed, rather than when it is in the process of overflowing.

This property allows you to specify that you want the positioned element initially displayed using the position try fallback option that gives its containing block the most width or most height. This is achieved by setting the most-height, most-width, most-block-size, or most-inline-size values. You can also remove the effects of any previously set position-try-order values using the normal value.

If no position-try fallback option is available that provides more width/height than the initial positioning assigned to the element, position-try-order has no effect.

Let's have a look at a demo that shows the effect of this property. The HTML is the same as in previous examples, except that we've added a <form> containing radio buttons, allowing you to select different values of position-try-order to see their effects.

We include a custom try fallback option — --custom-bottom — which positions the element below the anchor and adds a margin:

css
@position-try --custom-bottom {
  top: anchor(bottom);
  bottom: unset;
  margin-top: 10px;
}

We initially position the infobox at the top of the anchor, and then give it our custom try fallback:

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  bottom: anchor(top);
  margin-bottom: 10px;
  justify-self: anchor-center;
  position-try-fallbacks: --custom-bottom;
}

Finally, we include some JavaScript that sets a change event handler on the radio buttons. When a radio button is selected, its value is applied to the infobox's position-try-order property.

js
const infobox = document.querySelector(".infobox");
const radios = document.querySelectorAll('[name="position-try-order"]');

for (const radio of radios) {
  radio.addEventListener("change", setTryOrder);
}

function setTryOrder(e) {
  const tryOrder = e.target.value;
  infobox.style.positionTryOrder = tryOrder;
}

Try selecting the most-height order option. This has the effect of applying the --custom-bottom position try fallback option, which positions the element below the anchor. This occurs because there is more space below the anchor than there is above it.

Conditionally hiding anchor-positioned elements

In some situations, you might want to hide an anchor-positioned element. For example, if the anchor element is clipped because it's too close to the edge of the viewport, you might want to just hide its associated element altogether. The position-visibility property allows you to specify conditions under which positioned elements are hidden.

By default, the positioned element is always displayed. The no-overflow value will strongly hide the positioned element if it starts to overflow its containing element or the viewport.

The anchors-visible value, on the other hand, strongly hides the positioned element if its associated anchor(s) are completely hidden, either by overflowing its containing element (or the viewport) or being covered by other elements. The positioned element will be visible if any part of the anchor(s) is still visible.

A strongly hidden element acts as though it and its descendant elements have a visibility value of hidden set, regardless of what their actual visibility value is.

Let's see this property in action.

This example uses the same HTML and CSS as in the previous examples, with the infobox tethered to the anchor's bottom edge. The infobox is given position-visibility: no-overflow; to hide it completely when it is scrolled upwards to the point where it starts to overflow the viewport.

css
.infobox {
  position: fixed;
  position-anchor: --myAnchor;
  margin-bottom: 5px;
  position-area: top span-all;
  position-visibility: no-overflow;
}

Scroll down the page and note how the positioned element is hidden once it reaches the top of the viewport:

See also