{
"$type": "site.standard.document",
"content": {
"$type": "site.standard.content.markdown",
"text": "You’ve seen it before, an ‘x’ at the corner of a surface which will allow the user to exit that surface. In design, it seems simple to just place the button at the top corner. To engineer this, we have a few options:\n\n## Absolute Position\n\nThe first option most folks might consider is something like the following:\n\n```\n.surface {\n position: relative;\n}\n\n.surface button.close {\n position: absolute;\n /* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties */\n inset-block-start: 0; /* logical property for \"top\" */\n inset-inline-end: 0; /* logical property for \"right\" */\n}\n```\n\nMany design system component libraries use this approach:\n\n- [Ant Design System](https://ant.design/components/modal)\n- [Base Web (Uber)](https://baseweb.design/components/modal/)\n- [Carbon Design System (IBM)](https://carbondesignsystem.com/components/modal/code/)\n- [Chakra UI](https://chakra-ui.com/docs/components/modal/usage)\n- [Github Primer](https://primer.style/react/Dialog)\n- [Reach UI](https://reach.tech/dialog/)\n- [Stacks (StackOverflow)](https://stackoverflow.design/product/components/modals/)\n\nThe problem with this approach is the way that `position: absolute` works. When applying `position: absolute;` to an element, it takes that element out of the normal flow of the document. This means that other elements cannot interact with this element.\n\nIn some configurations for the above components, the content (usually the title) *could* appear visually layered below the close button.\n\n\n\nEven if you think you control the content, assume that you don’t. User preferences can influence the content display, either by size, translation, or otherwise. Be prepared for the content to vary.\n\nSome implementations solve this for by providing enough padding to the content area so the button never collides. However, this often results in an imbalance of padding and may not be desirable from design.\n\n\n\n## Flex header\n\nAnother method is to add the button as a flex child to a header of the surface.\n\n```\n.surface .header {\n display: flex;\n}\n\n.surface .header button.close {\n margin-inline-start: auto;\n}\n```\n\nHere’s a list of components across design systems using this approach:\n\n- [Atlassian Design](https://atlassian.design/components/modal-dialog/examples)\n- [Microsoft Fluent](https://developer.microsoft.com/en-us/fluentui#/controls/web/modal)\n- [Twilio Paste](https://paste.twilio.design/components/modal/)\n- [Shopify Polaris](https://polaris.shopify.com/components/modal#navigation)\n- [Shoelace UI](https://shoelace.style/components/dialog)\n\nWhile this avoids a collision between the button and content, it also requires that the header exist which will be at least the height of the close button. In cases where additional header content doesn’t exist this displays a large forehead before the content.\n\n\n\nSo if the surface doesn’t have a title, the amount of space might not be desirable by design. Certainly, if design is attempting to curate the title, this might have some control. However, incorrect alignment could also result in the close button centering within the header instead of pinning to the corner.\n\n\n\n## Unique alternatives\n\nThere are some other approaches. [Salesforce Lightning includes the button outside the modal](https://www.lightningdesignsystem.com/components/modals/) which might not work for other surfaces. [Their alert component](https://www.lightningdesignsystem.com/components/alert/) uses the `position: absolute;` technique as an example which will have similar problems as described above. [Adobe Spectrum avoids the ‘x’ button entirely](https://spectrum.adobe.com/page/alert-dialog/) and provides an explicit action to close the surface. However, [their alert component](https://spectrum.adobe.com/page/in-line-alert/) suffers from problems using the icon accessory in relation to the title in this similar layout.\n\n\n\n## The “buoyant” approach\n\nLet’s be clear about some requirements. If design is expecting a close button to appear at the top corner:\n\n- Padding around the surface should be visually consistent.\n- The close button should never move from the corner.\n- The content in the surface should not collide with the close button.\n\nThese requirements are meant for surfaces that are text heavy. A surface which has a full-bleed media (eg., image or video) which spans the width of the surface will have other problems. The normal `position: absolute;` treatment would probably work well in this case and then ensuring that the button has sufficient contrast against the media it appears above.\n\nWe can leverage two features of CSS to get an intended result.\n\n### Recent relative ancestor\n\nIn order to get an element to be positioned relative to another, we need to create a relationship. In the case of `position: absolute;` this relation doesn’t need to be between a direct parent and child. It can be **any ancestor**. This means we can have a distant child be positioned to ancestor located up the tree.\n\n```\n.surface {\n position: relative;\n}\n\n.surface button.close {\n position: absolute;\n}\n```\n\nThis is no different from the methods we’ve critized above *except* that the `button.close` element is a child of another element; our secret sauce…\n\n### Floats!\n\nThe way we get text to wrap around elements is by using the `float` property. We’ll float the button toward the right, so that content moves around it.\n\n```\nspan.floater {\n float: right; /* inline-end */\n}\n```\n\nWhile we’d like to use CSS Logical Properties for the float, [they aren’t well supported](https://developer.mozilla.org/en-US/docs/Web/CSS/float#browser_compatibility). You’ll probably need to adjust the type of float based on the `dir`.\n\n```\n[dir=\"rtl\"] span.floater {\n float: left;\n}\n```\n\nThen our HTML should be setup in the following manner:\n\n```\n<div class=\"surface\">\n <span class=\"floater\">\n <button class=\"close\">×</button>\n </span>\n <!-- Surface content goes here -->\n</div>\n```\n\nYou’ll need a bunch of other styles to finesse the size of the floating element and button in relation to the content. The final result should look something like this:\n\n<p>See the Pen <a href=\"https://codepen.io/fauxserious/pen/ExpWjzL\"> buoyant button</a> by Donnie D’Amato (<a href=\"https://codepen.io/fauxserious\">@fauxserious</a>) on <a href=\"https://codepen.io\">CodePen</a>.</p>\n\nLet me explain what’s going on here. In the demo above, the blue box represents the `span.floater` element dimensions. Note that because we’re using `position: absolute;` for the `button` element child, it’s not visually inside the `span.floater` but instead positioned relative to the surface. We set the dimensions of the `span.floater` so that the content won’t collide with the dimensions of the `button` and instead wraps around it.\n\nYou can remove the `demo` attribute from the `<closable-surface/>` element to see the final result without the blue box. I’m using a custom element so that it is easy to edit the content without accidentally altering the markup meant for the buoyant button.\n\nTry removing the title, editing the content, or resizing the window. The content should never collide with the button and always sit at the top corner.\n\n**#make-floats-great-again**",
"version": "1.0"
},
"description": "A recent bug turns into a realization that most close buttons across the web are engineered poorly.",
"path": "/posts/close-thy-enemy",
"publishedAt": "2023-01-09T00:00:00.000Z",
"site": "https://blog.damato.design",
"tags": [
"css"
],
"textContent": "You’ve seen it before, an ‘x’ at the corner of a surface which will allow the user to exit that surface. In design, it seems simple to just place the button at the top corner. To engineer this, we have a few options:\n\nAbsolute Position\n\nThe first option most folks might consider is something like the following:\n\nMany design system component libraries use this approach:\nAnt Design System\nBase Web (Uber)\nCarbon Design System (IBM)\nChakra UI\nGithub Primer\nReach UI\nStacks (StackOverflow)\n\nThe problem with this approach is the way that works. When applying to an element, it takes that element out of the normal flow of the document. This means that other elements cannot interact with this element.\n\nIn some configurations for the above components, the content (usually the title) could appear visually layered below the close button.\n\nEven if you think you control the content, assume that you don’t. User preferences can influence the content display, either by size, translation, or otherwise. Be prepared for the content to vary.\n\nSome implementations solve this for by providing enough padding to the content area so the button never collides. However, this often results in an imbalance of padding and may not be desirable from design.\n\nFlex header\n\nAnother method is to add the button as a flex child to a header of the surface.\n\nHere’s a list of components across design systems using this approach:\nAtlassian Design\nMicrosoft Fluent\nTwilio Paste\nShopify Polaris\nShoelace UI\n\nWhile this avoids a collision between the button and content, it also requires that the header exist which will be at least the height of the close button. In cases where additional header content doesn’t exist this displays a large forehead before the content.\n\nSo if the surface doesn’t have a title, the amount of space might not be desirable by design. Certainly, if design is attempting to curate the title, this might have some control. However, incorrect alignment could also result in the close button centering within the header instead of pinning to the corner.\n\nUnique alternatives\n\nThere are some other approaches. Salesforce Lightning includes the button outside the modal which might not work for other surfaces. Their alert component uses the technique as an example which will have similar problems as described above. Adobe Spectrum avoids the ‘x’ button entirely and provides an explicit action to close the surface. However, their alert component suffers from problems using the icon accessory in relation to the title in this similar layout.\n\nThe “buoyant” approach\n\nLet’s be clear about some requirements. If design is expecting a close button to appear at the top corner:\nPadding around the surface should be visually consistent.\nThe close button should never move from the corner.\nThe content in the surface should not collide with the close button.\n\nThese requirements are meant for surfaces that are text heavy. A surface which has a full-bleed media (eg., image or video) which spans the width of the surface will have other problems. The normal treatment would probably work well in this case and then ensuring that the button has sufficient contrast against the media it appears above.\n\nWe can leverage two features of CSS to get an intended result.\n\nRecent relative ancestor\n\nIn order to get an element to be positioned relative to another, we need to create a relationship. In the case of this relation doesn’t need to be between a direct parent and child. It can be any ancestor. This means we can have a distant child be positioned to ancestor located up the tree.\n\nThis is no different from the methods we’ve critized above except that the element is a child of another element; our secret sauce…\n\nFloats!\n\nThe way we get text to wrap around elements is by using the property. We’ll float the button toward the right, so that content moves around it.\n\nWhile we’d like to use CSS Logical Properties for the float, they aren’t well supported. You’ll probably need to adjust the type of float based on the .\n\nThen our HTML should be setup in the following manner:\n\nYou’ll need a bunch of other styles to finesse the size of the floating element and button in relation to the content. The final result should look something like this:\n\nSee the Pen buoyant button by Donnie D’Amato (@fauxserious) on CodePen.\n\nLet me explain what’s going on here. In the demo above, the blue box represents the element dimensions. Note that because we’re using for the element child, it’s not visually inside the but instead positioned relative to the surface. We set the dimensions of the so that the content won’t collide with the dimensions of the and instead wraps around it.\n\nYou can remove the attribute from the element to see the final result without the blue box. I’m using a custom element so that it is easy to edit the content without accidentally altering the markup meant for the buoyant button.\n\nTry removing the title, editing the content, or resizing the window. The content should never collide with the button and always sit at the top corner.\n\n#make-floats-great-again",
"title": "Close thy enemy",
"updatedAt": "2026-06-10T01:51:48.277Z"
}