Fix misbehaving grid children with display: contents CSS
I've been working on a project that involves creating a custom widget that's compatible with CKEditor 5. One of the problems I've been having is implementing a block widget for elements that are displayed as a grid. While the grid is functioning correctly, the widget element that's needed for editing functionality is adding a child element that's also being counted as a grid child.
Here's a pared-down example of the problem:
HTML:
<div class="wrapper">
<div class="col-container">
<div class="column">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dictum varius duis at consectetur. Gravida cum sociis natoque penatibus et magnis. Ut ornare lectus sit amet est placerat. Sapien et ligula ullamcorper malesuada proin. Maecenas accumsan lacus vel facilisis volutpat est velit egestas. Id porta nibh venenatis cras sed felis eget. Odio ut sem nulla pharetra diam sit amet. Eget velit aliquet sagittis id. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi. Tristique senectus et netus et malesuada. Vel turpis nunc eget lorem dolor. Laoreet suspendisse interdum consectetur libero id. Aliquam etiam erat velit scelerisque in dictum. Semper feugiat nibh sed pulvinar proin gravida. Massa ultricies mi quis hendrerit dolor magna. Risus feugiat in ante metus dictum at tempor commodo.
</div>
<div class="column">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dictum varius duis at consectetur. Gravida cum sociis natoque penatibus et magnis. Ut ornare lectus sit amet est placerat. Sapien et ligula ullamcorper malesuada proin. Maecenas accumsan lacus vel facilisis volutpat est velit egestas. Id porta nibh venenatis cras sed felis eget. Odio ut sem nulla pharetra diam sit amet. Eget velit aliquet sagittis id. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi. Tristique senectus et netus et malesuada. Vel turpis nunc eget lorem dolor. Laoreet suspendisse interdum consectetur libero id. Aliquam etiam erat velit scelerisque in dictum. Semper feugiat nibh sed pulvinar proin gravida. Massa ultricies mi quis hendrerit dolor magna. Risus feugiat in ante metus dictum at tempor commodo.
</div>
</div>
</div>
<template id="star-template">
<div class="star_container">
<svg xmlns="http://www.w3.org/2000/svg" class="star star_top" height="30" viewBox="0 -960 960 960" width="30" fill="#d4aa00" stroke="#000000" stroke-width="31.294488">
<path d="m305-704 112-145q12-16 28.5-23.5T480-880q18 0 34.5 7.5T543-849l112 145 170 57q26 8 41 29.5t15 47.5q0 12-3.5 24T866-523L756-367l4 164q1 35-23 59t-56 24q-2 0-22-3l-179-50-179 50q-5 2-11 2.5t-11 .5q-32 0-56-24t-23-59l4-165L95-523q-8-11-11.5-23T80-570q0-25 14.5-46.5T135-647l170-57Z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="star star_bottom" height="30" viewBox="0 -960 960 960" width="30" fill="#d4aa00" stroke="#000000" stroke-width="31.294488">
<path d="m305-704 112-145q12-16 28.5-23.5T480-880q18 0 34.5 7.5T543-849l112 145 170 57q26 8 41 29.5t15 47.5q0 12-3.5 24T866-523L756-367l4 164q1 35-23 59t-56 24q-2 0-22-3l-179-50-179 50q-5 2-11 2.5t-11 .5q-32 0-56-24t-23-59l4-165L95-523q-8-11-11.5-23T80-570q0-25 14.5-46.5T135-647l170-57Z"/>
</svg>
</div>
</template>
The top chunk of HTML is the grid that I'm starting with. There are two columns that will fill the .col-container
space. Below that is a template that will be added to the .col-container
with JavaScript, making it a child of the .col-container
element. The template has two SVG star elements that will be absolutely positioned at the top and bottom of the parent element.
JavaScript:
let column_container = document.querySelector('.col-container');
let template = document.querySelector('#star-template');
let clone = template.content.cloneNode(true);
column_container.appendChild(clone);
The CSS creates the grid and auto-fits the columns. If the columns become smaller than 100px, the right-most columns will begin to wrap into the row below. This CSS also positions the star icons similarly to the widget element I've been having problems with. I also added a couple of animations on hover because I wanted to.
CSS:
body {
display: flex;
justify-content: center;
font-family: sans-serif;
background: white;
}
.wrapper {
max-width: 800px;
border: 1px solid #000;
margin-top: 20px;
}
.col-container {
position: relative;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-gap: 10px;
}
.column {
padding: 10px;
border: 1px dashed lightseagreen;
}
.star {
width: 30px;
height: 30px;
position: absolute;
display: block;
transition: transform 0.3s, fill 0.3s;
}
.star_bottom {
bottom: 0;
right: min(10%, 30px);
transform: translateY(50%);
}
.star_bottom:hover {
transform: translateY(50%) scale(2) rotate(145deg);
}
.star_top {
top: 0;
left: min(10%, 30px);
transform: translateY(-50%);
}
.star:hover {
fill: #bada55;
}
.star_top:hover {
transform: translateY(-50%) scale(2) rotate(145deg);
}
This is what the grid looks like with the star icons added:
You can see that the right third of the grid is taken up with blank space. This is the wrapper element being counted as a child of the grid.
The solution is to use the display: contents property on the #star-template
element. This property makes the template element be ignored by the parent and its children are treated as if they were direct children of the parent element. Since the children are both absolutely positioned, they don't factor into the grid at all.
The one downside is that browser support still isn't great. Can I use shows that modern browsers offer partial support of display: contents
. Buttons aren't accessible when it's used in all browsers except the latest Safari on iOS.
Here's the CSS that I added to make it work:
.star_container {
display: contents;
}
Now you can see that both columns reach the edges of their parent element, and the star container is nowhere to be found, however, the stars are still in the correct locations.