{
  "$type": "site.standard.document",
  "content": {
    "$type": "site.standard.content.markdown",
    "text": "import PlaceholderImage from '../../components/PlaceholderImage.astro';\nimport HasDemo1 from '../../components/demos/HasDemo1.astro';\nimport HasDemo2 from '../../components/demos/HasDemo2.astro';\nimport HasDemo3 from '../../components/demos/HasDemo3.astro';\nimport FYIBlock from \"../../components/FYIBlock.astro\";\n\n<FYIBlock type=\"success\">\n\t<span slot=\"title\">Update - 23rd December, 2024</span>\n\t<div slot=\"body\">This is now supported in all evergreen browsers, so need for an enabled flag to deal with this support - hooray!</div>\n</FYIBlock>\n\n## The problem with siblings\n\nIt's been notoriously difficult to select elements depending on their children (almost as hard as that one level on Crash Bandicoot when you have to ride a tiger down the Great Wall of China, or that level on Max Payne with the crying baby and the blood line path), or siblings that came before it. Super easy to say _\"let's make this box red when it is an element following on from an element with a class of outline\"_.\n\n`.outline + .red`\n\n<style>{`\n  .box {\n  \twidth: 100%;\n    height: auto;\n    aspect-ratio: 1;\n    background-color: var(--color-dark);\n  }\n\n  .outline {\n    background-color: white;\n    border: 1px solid color-mix(in srgb, var(--color-dark) 25%, transparent);\n  }\n\n  #one-red-box .outline + .box,\n  .outline + .box + .box + .box + .box {\n    background-color: #b94a38;\n  }\n\n  .box-row {\n    display: flex;\n    column-gap: 25px;\n    margin-block: 20px;\n    background-color: var(--color-surface);\n    border: 1px solid color-mix(in srgb, var(--color-dark) 50%, transparent);\n    padding: 15px;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"one-red-box\">\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n</div>\n\nBut how do we get the 3rd or 4th element away from that outlined box, and make it red? Keep on chaining selectors, like this `.outline + .box + .box + .box + .box`?\n\n```css\n.outline + .box,\n.outline + .box + .box + .box + .box {\n  background-color: #b94a38;\n}\n```\n\n<div class=\"box-row\">\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\nYeah that did it too....but blimey it's awful isn't it. We also can't make the 1st box red if the third box our outlined box, based on the `.outline` class. There's no example I can think of that would demonstrate how it doesn't work - feel free to enlighten me and I'll add it to this post.\n\n## Enter :has()\n\nWith this complication comes the perfect excuse to take `:has()` for a spin. We can use it to style our intended box red, when the element after it has the `.outline` class.\n\n```css\n.box:has( + .outline) {\n  background-color: #b94a38;\n}\n```\n\n<style>{`\n  #has-example-row .box:has( + .outline) {\n    background-color: #b94a38;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\n### Adding hover states\n\nSo given that we've got access to previous siblings, we've got the ability to add states to them. Make that red box turn orange on hover? No problemo.\n\n```css\n.box:has( + .outline) {\n  background-color: #b94a38;\n  transition: .3s ease-in-out background-color;\n}\n\n/* Apply this when our box is hovered AND\nhas the outline classed box next to it */\n.box:hover:has( + .outline) {\n  background-color: #ff5f1f;\n}\n```\n\n<style>{`\n  #has-example-row-2 .box:has( + .outline) {\n    background-color: #b94a38;\n    transition: .3s ease-in-out background-color;\n  }\n  #has-example-row-2 .box:hover:has( + .outline) {\n    background-color: #ff5f1f;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-2\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\n## Flipping the relationship\n\nWhat if we _flipped_ this, and have the red box go orange when the **outlined** box is hovered? With `:has()` it's easy enough.\n\n```css\n.box:has( + .outline) {\n  background-color: #b94a38;\n  transition: .3s ease-in-out background-color;\n}\n\n/* Apply this when our box is hovered AND\nhas the outline classed box next to it */\n.box:has( + .outline:hover) {\n  background-color: #ff5f1f;\n}\n```\n<style>{`\n  #has-example-row-3 .box:has( + .outline) {\n    background-color: #b94a38;\n    transition: .3s ease-in-out background-color;\n  }\n  #has-example-row-3 .box:has( + .outline:hover) {\n    background-color: #ff5f1f;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-3\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\n## Selecting all siblings at once\n\nSee where this is going? Now, we're going to move our selector from `.box` to the parent `.box-row` and use a `*` to select all the elements in the row, turning them orange.\n\n<style>{`\n  #has-example-row-4 > * {\n    background-color: color-mix(in srgb, var(--color-feature) 100%, transparent);;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-4\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\nThen we'll change it, using `:has()` on the parent element to check if any of the `.outline` classes are hovered. Then we can apply a style using `:not()`, passing the `:hover` state through. Effectively saying that in this block, any boxes that aren't hovered, should turn orange when our `.outline` class is hovered.\n\n```css\n#has-example-row-5:has( .outline:hover ) > *:not(:hover) {\n  background-color: #ff5f1f;\n}\n```\n\n<style>{`\n  #has-example-row-5:has( .outline:hover ) > *:not(:hover) {\n    background-color: #ff5f1f;\n    transition: .3s ease-in-out background-color;\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-5\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box outline\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\nAlmost there. Let's remove `.outline` from our statement, replacing it with another `*` selector. So now in this block, any boxes that aren't hovered, should turn orange when ~~our `.outline` class~~ any of our elements are hovered.\n\n```css\n#has-example-row-6:has( *:hover ) > *:not(:hover) {\n  background-color: #ff5f1f;\n}\n```\n\n<style>{`\n  #has-example-row-6 > * {\n    transition: .3s ease-in-out background-color;\n  }\n\n  #has-example-row-6:has( *:hover ) > *:not(:hover) {\n    background-color: var(--color-feature, #ff5f1f);\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-6\">\n  <div class=\"box \"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n  <div class=\"box\"></div>\n</div>\n\n## Blurring the rest\n\nWe're so close, but only because the rest is not specifically `:has()` related. We're going to use `placeskull.com` to provide some images in our row blocks. Now we don't benefit from the background color, let's use `filter: blur(4px)` and apply that to the image at the end of our `:has()` statement. We'll also need `overflow: hidden` on the images parent div to prevent the blur from going over the edges - this keeps it nice and contained.\n\n```css\n#has-example-row-7 > * {\n  transition: .3s ease-in-out all;\n}\n\n#has-example-row-7:has( *:hover ) > *:not(:hover) img {\n  filter: blur(4px);\n}\n```\n\n<style>{`\n  #has-example-row-7 > * {\n    overflow: hidden;\n  }\n\n  #has-example-row-7 > *{\n    transition: .3s ease-in-out all;\n  }\n\n  #has-example-row-7:has( *:hover ) > *:not(:hover) {\n    filter: blur(4px);\n  }\n`}</style>\n\n<div class=\"box-row\" id=\"has-example-row-7\">\n  <div class=\"box\">\n    <PlaceholderImage ratio=\"1 / 1\" />\n  </div>\n  <div class=\"box\">\n  \t<PlaceholderImage ratio=\"1 / 1\" />\n  </div>\n  <div class=\"box\">\n  \t<PlaceholderImage ratio=\"1 / 1\" />\n  </div>\n  <div class=\"box\">\n  \t<PlaceholderImage ratio=\"1 / 1\" />\n  </div>\n  <div class=\"box\">\n  \t<PlaceholderImage ratio=\"1 / 1\" />\n  </div>\n</div>\n\nLastly, we'll put a `transform: scale()` on the image, and also add a background color to the images parent block to be able to cover the whitespace caused by the scale, and voila!\n\n<style>{`\n  .main-container {\n    border: 1px solid color-mix(in srgb, var(--color-dark) 50%, transparent);\n    padding: 20px;\n    margin-block: 20px;\n    position: relative;\n  }\n\n  .has-grid {\n    display: grid;\n    grid-template-columns: repeat(8, minmax(0, 1fr));\n    gap: 1rem;\n    place-content: center;\n  }\n\n  @media (max-width: 768px) {\n    .has-grid {\n      grid-template-columns: repeat(4, minmax(0, 1fr));\n    }\n  }\n\n  .cell {\n    grid-column: span 2/span 2;\n    overflow: hidden;\n    background: black;\n  }\n\n  .cell {\n    width: 100%;\n    {/* transition: .65s cubic-bezier(0.680, -0.550, 0.265, 1.550) all; */}\n    transition: all 0.25s ease-in-out;\n  }\n\n  .has-grid:has( > *:hover ) > *:not(:hover) {\n    transition: all 0.25s ease-in-out;\n    filter: blur(4px);\n    transform: scale(0.95);\n  }\n\n  .attribute {\n    position: absolute;\n    bottom: -20px;\n    left: 0;\n    right: 0;\n    margin-inline: auto;\n    text-align: center;\n    max-width: 750px;\n    font-family: 'Erode', sans-serif;\n  }\n\n  .attribute a {\n    font-weight: 500;\n  }\n\n  .helpful-hint {\n    margin-block: 40px;\n    font-size: 1.85rem;\n    text-align: center;\n    font-family: 'Erode', sans-serif;\n  }\n`}</style>\n\n<div class=\"main-container\">\n\t<div class=\"has-grid\">\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t\t<div class=\"cell\">\n\t\t\t<PlaceholderImage ratio=\"1 / 1\" />\n\t\t</div>\n\t</div>\n</div>\n\n## Where :has() really earns its keep\n\nOur demo above is a pretty straightforward example of :has() in action, but it really shows it's worth when it's replacing JavaScript. Below are three real-world patterns that benefit from using :has() instead.\n\n### Form state styling\n\nThe demo below shows how to highlight a label or fieldset when the input inside it is invalid.\n\n```css\n.field:has(input:invalid) {\n  --field-border: var(--color-error);\n  --field-label: var(--color-error);\n}\n```\n\nThe `.field` wrapper responds to how valid the input inside it is. One rule handles every field in the form.\n\n<HasDemo1 />\n\n### Card selection with sibling dimming\n\nWhen one card is selected, dim the others without toggling classes or writing JS.\n\n```css\n.cards:has(input:checked) .card:not(:has(input:checked)) {\n  opacity: 0.35;\n}\n```\n\nThe `.cards` grid detects any checked input and targets every sibling that doesn't have one. The selection state lives in the checkbox and `:has()` does the rest.\n\n<HasDemo2 />\n\n### Card grid layout\n\nSwitch a grid from two columns to three when a third card is present - no container queries, no JS counting children.\n\n```css\n.card-grid {\n  grid-template-columns: repeat(2, 1fr);\n}\n\n.card-grid:has(.card:nth-child(3)) {\n  grid-template-columns: repeat(3, 1fr);\n}\n```\n\nThe grid adapts to its own content, updating the layout according to the amount of cards present.\n\n<HasDemo3 />",
    "version": "1.0"
  },
  "description": "Reaching an element's siblings in CSS used to be tough. :has() changes everything.",
  "path": "/writing/selecting-all-siblings-with-has",
  "publishedAt": "2023-04-26T00:00:00.000Z",
  "site": "https://dominickjay.com",
  "tags": [
    "css"
  ],
  "textContent": "import PlaceholderImage from '../../components/PlaceholderImage.astro';\nimport HasDemo1 from '../../components/demos/HasDemo1.astro';\nimport HasDemo2 from '../../components/demos/HasDemo2.astro';\nimport HasDemo3 from '../../components/demos/HasDemo3.astro';\nimport FYIBlock from \"../../components/FYIBlock.astro\";\n\n\tUpdate - 23rd December, 2024\n\tThis is now supported in all evergreen browsers, so need for an enabled flag to deal with this support - hooray!\n\nThe problem with siblings\n\nIt's been notoriously difficult to select elements depending on their children (almost as hard as that one level on Crash Bandicoot when you have to ride a tiger down the Great Wall of China, or that level on Max Payne with the crying baby and the blood line path), or siblings that came before it. Super easy to say \"let's make this box red when it is an element following on from an element with a class of outline\".\n\n{}\n\n  \n  \n  \n  \n  \n\nBut how do we get the 3rd or 4th element away from that outlined box, and make it red? Keep on chaining selectors, like this ?\n\n  \n  \n  \n  \n  \n\nYeah that did it too....but blimey it's awful isn't it. We also can't make the 1st box red if the third box our outlined box, based on the  class. There's no example I can think of that would demonstrate how it doesn't work - feel free to enlighten me and I'll add it to this post.\n\nEnter :has()\n\nWith this complication comes the perfect excuse to take  for a spin. We can use it to style our intended box red, when the element after it has the  class.\n\n{}\n\n  \n  \n  \n  \n  \n\nAdding hover states\n\nSo given that we've got access to previous siblings, we've got the ability to add states to them. Make that red box turn orange on hover? No problemo.\n\n{}\n\n  \n  \n  \n  \n  \n\nFlipping the relationship\n\nWhat if we flipped this, and have the red box go orange when the outlined box is hovered? With  it's easy enough.\n\n{}\n\n  \n  \n  \n  \n  \n\nSelecting all siblings at once\n\nSee where this is going? Now, we're going to move our selector from  to the parent  and use a  to select all the elements in the row, turning them orange.\n\n{}\n\n  \n  \n  \n  \n  \n\nThen we'll change it, using  on the parent element to check if any of the  classes are hovered. Then we can apply a style using , passing the  state through. Effectively saying that in this block, any boxes that aren't hovered, should turn orange when our  class is hovered.\n\n{}\n\n  \n  \n  \n  \n  \n\nAlmost there. Let's remove  from our statement, replacing it with another  selector. So now in this block, any boxes that aren't hovered, should turn orange when ~~our  class~~ any of our elements are hovered.\n\n{}\n\n  \n  \n  \n  \n  \n\nBlurring the rest\n\nWe're so close, but only because the rest is not specifically  related. We're going to use  to provide some images in our row blocks. Now we don't benefit from the background color, let's use  and apply that to the image at the end of our  statement. We'll also need  on the images parent div to prevent the blur from going over the edges - this keeps it nice and contained.\n\n{}\n\n  \n    \n  \n  \n  \t\n  \n  \n  \t\n  \n  \n  \t\n  \n  \n  \t\n  \n\nLastly, we'll put a  on the image, and also add a background color to the images parent block to be able to cover the whitespace caused by the scale, and voila!\n\n{}\n\n\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\n\t\n\nWhere :has() really earns its keep\n\nOur demo above is a pretty straightforward example of :has() in action, but it really shows it's worth when it's replacing JavaScript. Below are three real-world patterns that benefit from using :has() instead.\n\nForm state styling\n\nThe demo below shows how to highlight a label or fieldset when the input inside it is invalid.\n\nThe  wrapper responds to how valid the input inside it is. One rule handles every field in the form.\n\nCard selection with sibling dimming\n\nWhen one card is selected, dim the others without toggling classes or writing JS.\n\nThe  grid detects any checked input and targets every sibling that doesn't have one. The selection state lives in the checkbox and  does the rest.\n\nCard grid layout\n\nSwitch a grid from two columns to three when a third card is present - no container queries, no JS counting children.\n\nThe grid adapts to its own content, updating the layout according to the amount of cards present.",
  "title": "Selecting all siblings with the :has() function",
  "updatedAt": "2026-06-08T09:40:23.065Z"
}