Web UI has been evolving faster than ever
Separate your application logic from your styling logic
Stop writing unnecessary, heavy, thread-blocking JavaScript
popover attribute
popover attributeBuilt-in accessibility via keyboard behavior, tab focus management, top-layer support, and (optional) light-dismiss
May 2023
September 2023
May 2024 (soon)
<!-- Button -->
<button popovertarget="my-popover">
Open Me
</button>
<!-- Popover -->
<div id="my-popover" popover>
... Popover Stuff ...
</div>
<button popovertarget="my-popover">
Open Popover
</button>
<div id="my-popover" popover>
<p>I am a popover with more information.<p>
</div>
<button popovertarget="my-popover">
Open Popover
</button>
<div id="my-popover" popover="manual">
<button class="close-btn"
popovertarget="my-popover"
popovertargetaction="hide">
<span aria-hidden=”true”>❌</span>
<span class="sr-only">Close</span>
</button>
<p>I am a popover with more information.<p>
</div>
.settings-popover {
&:popover-open {
/* 0. BEFORE-OPEN */
@starting-style {
transform: translateY(20px);
opacity: 0;
}
/* 1. OPEN STATE */
transform: translateY(0);
opacity: 1;
}
/* 2. EXIT STATE */
transform: translateY(-50px);
opacity: 0;
/* Transitioning properties + allow-discrete */
transition: transform 0.5s,
opacity 0.5s,
display 0.5s allow-discrete;
}
// JavaScript
deleteListItem() {
const listItem = this.parentNode;
listItem.classList.add('is-deleting');
setTimeout(() => {
listItem.parentNode.removeChild(listItem);
}, 500);
}
/* CSS */
.item {
opacity: 1;
transform-origin: bottom;
transition: opacity 0.5s,
transform 0.5s,
display 0.5s allow-discrete;
}
@starting-style {
.item {
opacity: 0;
height: 0;
}
}
/* while deleting, before DOM removal in JS */
.is-deleting {
opacity: 0;
display: none;
transform: skewX(50deg) translateX(-25vw);
}
<!-- Add an id -->
<button popovertarget="my-popover" id="toggle-btn">
Open Me
</button>
<!-- Add an anchor -->
<div popover id="my-popover" anchor="toggle-btn">
Popover Stuff
</div>
#profile-settings-popover {
bottom: calc(anchor(top) + var(--spacer));
right: calc(anchor(right));
}
#profile-settings-popover {
inset-area: top span-left;
}
una.github.io/anchor-tool/
[popover] {
position: absolute;
inset-area: top span-left;
margin-bottom: 1rem;
}
.item {
--radius: calc(var(--btn-size) + var(--extra-space));
background-color: var(--bg);
transform: translateX(calc(cos(var(--angle)) * var(--radius)))
translateY(calc(sin(var(--angle) * -1) * var(--radius)));
transition: transform 0.3s var(--delay) ease;
}
popover has no default semantic rolerole="menu"role="tooltip"dialog elementselectlist element 👩🏼🔬select v2A selectable list of options you can fully customize, with both style and content, building on the existing select element
❌
❌
❌
Stylable, accessible dropdowns are coming!!!
Search by region
<select class="country-select">
<button>
<selectedoption></selectedoption>
</button>
<datalist>
<option value="" hidden>
<p>Select a country</p>
</option>
<option value="andorra">
<img src="../Andorra.svg" />
<p>Andorra</p>
</option>
<option value="bolivia">
<img src="../Bolivia.svg.png" />
<p>Bolivia</p>
</option>
...
</datalist>
</select>
/* Fallback for older browsers */
<select class="country-select">
<button>
<selectedoption></selectedoption>
</button>
<datalist>
<option value="" hidden>
<p>Select a country</p>
</option>
<option value="andorra">
<img src="../Andorra.svg" />
<p>Andorra</p>
</option>
<option value="bolivia">
<img src="../Bolivia.svg.png" />
<p>Bolivia</p>
</option>
...
</datalist>
</select>
view-timelineanimation-timelineview()
May 2023
❌
Behind Flag
@keyframes fly-in {
0% {
opacity: 0;
transform: translateX(-100px);
}
50% {
opacity: 1;
transform: translateX(0px);
}
}
blockquote {
animation: fly-in auto linear;
animation-timeline: view();
}
document.querySelectorAll('.delete-btn')
.forEach(btn => {
btn.addEventListener('click', () => {
btn.parentElement
.classList.add('fade-out');
}
)
})
document.querySelectorAll('.delete-btn')
.forEach(btn => {
btn.addEventListener('click', () => {
if (document.startViewTransition) {
document.startViewTransition(() => {
btn.parentElement.remove();
});
} else {
btn.parentElement.remove();
}
}
)
})
:has() relational selectorStyle parent elements (and more) based on the presence or state of child elements
Aug 2022
Mar 2022
December 2023
:has() relational selector
This figure doesn't have a figcaption. Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet ipsam, ad delectus doloremque blanditiis in incidunt numquam eum ullam ut quo porro vel soluta deleniti rerum reprehenderit quae aliquid quos.
This figure does have a figcaption. Lorem ipsum dolor sit amet consectetur adipisicing elit. Nostrum, aut. Minima natus nulla, dicta, quasi autem a architecto, earum explicabo molestias illum eum rerum magni! Harum sequi cumque quia expedita.
Style based on the number of children
:has(:nth-child(n + x))
@supports selector(:has(a)) {
.post {
display: none;
}
.filter-bar:has(#css-tag:checked) ~ .posts .post:has([data-tag="CSS"]),
.filter-bar:has(#html-tag:checked) ~ .posts .post:has([data-tag="HTML"]),
.filter-bar:has(#js-tag:checked) ~ .posts .post:has([data-tag="JavaScript"]),
... {
display: block;
}
}
@property declaration
@property --colorPrimary {
syntax: '<color>';
initial-value: magenta;
inherits: false;
}
@property Typed CSS
.card {
background-color: var(--colorPrimary); /* magenta */
}
.highlight-card {
--colorPrimary: yellow;
background-color: var(--colorPrimary); /* yellow */
}
.another-card {
--colorPrimary: 23;
background-color: var(--colorPrimary); /* magenta */
}
@property --gradPoint {
syntax: '<percentage>';
inherits: false;
initial-value: 40%;
}
.post {
background:
linear-gradient(var(--color1) var(--gradPoint),
var(--color2) calc(var(--gradPoint) + 20%));
transition: --gradPoint 0.5s;
}
.post:hover, .post:focus {
--gradPoint: 100%;
}
Went Stable Cross-browser recently
Old
@media (max-width: 400px) {
...
}
@container (min-inline-size: 300px)
and (max-inline-size: 599px) {
...
}New
@media (width <= 400px) {
...
}
@container (300px <= inline-size < 600px) {
...
}
This:
.card {
/* card styles */
}
.card .title {
/* element styles */
}
.card:hover {
/* modifier styles */
}
.card:focus {
/* modifier styles */
}
@container (width >= 300px) {
.card {
/* CQ styles */
}
}
Becomes...
.card {
/* card styles */
.title {
/* element styles */
}
&:hover, &:focus {
/* modifier styles */
}
@container (width >= 300px) {
/* CQ styles */
}
}🤫 This presentation is fully built with vanilla CSS & HTML*
*with some iframes for demos due to laziness
<style> with display:block and contenteditablescroll-snapaccesskeyLeverage the power of the browser
Reduce complexity, improve velocity
Stay in the know