Modernes CSS

bei


							--background-color--darkmode: 
								{{ background_color | transform_hex_to_dark_hsl }}
						

						grid-template-columns: 
							repeat(auto-fit, minmax(min(100%, rem(352px)), 1fr));
						

						max-width: calc(
							12ch + 
							var(--audio-player-icon-size) + 
							var(--z-ds-space-xs) + 
							2 * var(--audio-player-padding-inline)
						);
						

						@supports (font-size: 1cqi) {
							font-size: clamp(rem(82px), 8.2cqi, rem(164px));
						}
						

Hi, ich bin Thomas 👋

Senior Frontend Developer
bei ZEIT Online

#a11y, #leanweb, #webperf

CSS bei ZON


							<div class="cp-area cp-area--lead">
								<article class="zon-teaser zon-teaser--wide">
									<figure class="zon-teaser__media zon-teaser__media--desktop-wide zon-teaser__media--mobile-square">...</figure>    
									<h3 class="zon-teaser__heading">
										<span class="zon-teaser__title">...</span>
									</h3>
								</article>
							</div>
						

							.zon-teaser--wide {
								display: flex;
								gap: var(--z-ds-space-m);
								margin-bottom: var(--z-ds-space-teaser);

								@include respond-min($break-tablet-min) {
									gap: var(--z-ds-space-l);
								}
							}
							.zon-teaser--wide .zon-teaser__media {
								max-width: $ds-content-width;
							}
						

CSS Variablen

Dark Mode früher


							body {
								background: white;
								color: black;
							}
							@media (prefers-color-scheme: dark) {
								.body {
									background: black;
									color: white;
								}
							}
						

Dark Mode mit CSS Vars


							:root {
								--z-ds-color-text-100: #252525; // Primary Information
								--z-ds-color-text-70: #444; // Secondary Information
								--z-ds-color-text-40: #999; // Low Prio
								--z-ds-color-background-0: #fff; // Primary
							}
						

							:root {
								@media (prefers-color-scheme: dark) {
									--z-ds-color-text-100: #fff;
									--z-ds-color-text-70: #bababa;
									--z-ds-color-text-40: #8b8b8b;
									--z-ds-color-background-0: #121212;
								}
							}
						

							body {
								background: var(--z-ds-color-background-0);
								color: var(--z-ds-color-text-100);
							}
						

CSS Vars: Spacing


							--z-ds-space-xxs: #{remify(4px)};
							--z-ds-space-xs: #{remify(8px)};
							--z-ds-space-s: #{remify(12px)};
							--z-ds-space-m: #{remify(16px)};
							--z-ds-space-l: #{remify(20px)};
							--z-ds-space-xl: #{remify(24px)};
							--z-ds-space-xxl: #{remify(32px)};

							--z-ds-space-gap: #{remify(20px)};
							--z-ds-space-teaser: #{remify(24px)};
						

							@include respond-min($break-tablet-min) {
								--z-ds-space-xxs: #{remify(6px)};
								--z-ds-space-xxl: #{remify(54px)};
								--z-ds-space-gap: #{remify(32px)};
								--z-ds-space-teaser: #{remify(54px)};
							}
						

CSS Vars: Font Sizes


							--z-ds-fontsize-s: #{remify(20px)};
							--z-ds-fontsize-m: #{remify(20px)};
							--z-ds-fontsize-l: #{remify(22px)};

							@include respond-min($break-tablet-min) {
								--z-ds-fontsize-m: #{remify(22px)};
								--z-ds-fontsize-l: #{remify(24px)};
							}

							.zon-teaser h2 {
								font-size: var(--z-ds-fontsize-m);
							}

							.zon-teaser--large h2 {
								font-size: var(--z-ds-fontsize-l);
							}
						

CSS Vars: local & modified


						.audio-player {
							--audio-player-color-background: var(--z-ds-color-background-10);
							--audio-player-color-text: var(--z-ds-color-text-70);
							--audio-player-icon-size: #{rem(14px)};
							--audio-player-padding-block: #{rem(8px)};
							--audio-player-padding-inline: var(--z-ds-space-s);

							background-color: var(--audio-player-color-background);

							/* Need static width for animation.
							Is calculated with character count, icon width, flexbox gap plus padding
							text + icon width + flexbox gap + padding-left + padding-right */
							/* stylelint-disable order/properties-alphabetical-order */
							// prettier-ignore
							max-width: calc(
								12ch + var(--audio-player-icon-size) + var(--z-ds-space-xs) + 2 * var(--audio-player-padding-inline)
							);

							/* stylelint-enable */
						}

						.audio-player--large {
							--audio-player-color-background: var(--z-ds-color-text-100);
							--audio-player-color-text: var(--z-ds-color-background-20);
							--audio-player-icon-size: #{rem(18px)};
							--audio-player-padding-block: #{rem(12px)};
							--audio-player-padding-inline: var(--z-ds-space-m);
						}

						.audio-player__icon--replay {
							--audio-player-icon-size: #{rem(18px)};
						}
						

CSS Vars per JS setzen


						/* JS */
						this.indicator.style.setProperty(
							'--number-of-dots', this.dotsVisible);
						

						/* CSS */
						.gallery__indicator {
							width: calc((var(--number-of-dots) + 1) * 1ch);
						}
						

						this.header.style.setProperty(
							'--nav-topic-height', `${topicHeight}px`);

						activeSlide.style.setProperty(
							'--progress', `${oldProgress + 1}%`);
						

						
					

Farben

Farben als Kontext aus dem CMS

CSS Vars im DOM

HTML

							<section
								class="area--colored area--is-dark"
								style="--background-color: #293443; 
									--background-color--darkmode: hsl(215, 24%, 21%)">
						
CSS

							.area--colored {
								background-color: var(--background-color, '#fff8e7');
								@include dark-mode {
									background-color: var(--background-color--darkmode);
								}
							}
						

Automatischer Dark-Mode

Generierung der Farben

HTML

							<section
								class="area--colored area--is-dark"
								style="--background-color: #293443; 
									--background-color--darkmode: hsl(215, 24%, 21%)">
						
Jinja (Template)

							<section class="area--colored 
								area--is-{{ background_color | get_luminance_modifier) }}"
								style="--background-color: {{ background_color }}; 
									--background-color--darkmode: 
										{{ background_color | transform_hex_to_dark_hsl }}">
						

Farbfunktionen


						@zeit.web.register_test
						def valid_hex(hex):
						    if not hex:
						        return None
						    hex = hex.lstrip('#')
						    match = re.search(r'^(?:[0-9a-fA-F]{6})$', hex)
						    if match:
						        return True
						    return False


						@zeit.web.register_filter
						def color_is_dark(hexcolor):
						    if not hexcolor:
						        return None
						    # takes a hex background color (no shorthands) and computes
						    # if the color is dark in accessibility context
						    # i.e. a light color needs to be used for contrast
						    # like in light font on dark background
						    threshold = 0.5
						    try:
						        # we must manually filter out the hex sign, if color should contain it
						        hexcolor = hexcolor[1:] if hexcolor[0] == '#' else hexcolor

						        red, green, blue = tuple(int(hexcolor[i:i + 2], 16) for i in (0, 2, 4))
						        # perceived lightness by W3C working draft
						        # https://www.w3.org/TR/AERT/#color-contrast
						        luminance = (red * 0.299 + green * 0.587 + blue * 0.114) / 255
						        return luminance < threshold
						    except Exception:
						        return None


						@zeit.web.register_filter
						def get_luminance_modifier(hexcolor):
						    return 'dark' if color_is_dark(hexcolor) else 'light'


						@zeit.web.register_filter
						def transform_hex_to_dark_hsl(hexcolor):
						    hue, saturation, luminance = zeit.web.core.utils.hex_to_hsl(hexcolor)
						    if not color_is_dark(hexcolor):
						        saturation = saturation - 20 if saturation >= 20 else 0
						        luminance = 30
						    return f'hsl({hue}, {saturation}%, {luminance}%)'


						@zeit.web.register_filter
						def transform_hex_to_rgb(hexcolor):
						    if not valid_hex(hexcolor):
						        return None
						    return ', '.join(map(str, zeit.web.core.utils.hex_to_rgb(hexcolor)))


						@zeit.web.register_filter
						def transform_hex_to_rgba(hexcolor, alpha=1):
						    if not valid_hex(hexcolor):
						        return None
						    red, green, blue = zeit.web.core.utils.hex_to_rgb(hexcolor)
						    return f'rgba({red}, {green}, {blue}, {alpha})'
						

Viel Python Logik -> einfaches CSS *

Alpha / Mix Blend Mode

Podcasts

Podcast-Teaser

rgba  für Hover


							--z-ds-color-general-black-60: rgba(0, 0, 0, 0.6);
							--z-ds-color-general-white-60: rgba(255, 255, 255, 0.6);
						

Mix Blend Mode


							mix-blend-mode: multiply;
						
*, **

Logische Properties

Häufige Situation mit Margins


							.article__item {

								margin: 30px 20px;

								@include respond-min($break-tablet-min) {
									margin: 30px auto;
								}

								@include respond-min($break-desktop-min) {
									margin: 60px auto;
								}
							}
						

Häufige Situation mit Margins


							.article__item {

								margin: 30px 20px;

								@include respond-min($break-tablet-min) {
									margin-left auto;
									margin-right: auto;
								}

								@include respond-min($break-desktop-min) {
									margin-top: 60px;
									margin-bottom: 60px;
								}

							}
						

(Eine Lösung)


							--margin-vertical: 30px;
							--margin-horizontal: 20px;

							@include respond-min($break-tablet-min) {
								--margin-horizontal: auto;
							}

							@include respond-min($break-desktop-min) {
								--margin-vertical: 60px;
							}

							.article__item {
								margin: var(--margin-vertical) var(--margin-horizontal);
							}
						

Margin in eine Richtung ändern


							.article__item {

								margin: 30px 20px;

								@include respond-min($break-tablet-min) {
									margin-left auto;
									margin-right: auto;
								}

								@include respond-min($break-desktop-min) {
									margin-top: 60px;
									margin-bottom: 60px;
								}

							}
						

margin-[ inline|block ]


							.article__item {

								margin: 30px 20px;

								@include respond-min($break-tablet-min) {
									/* margin-left auto; */
									/* margin-right: auto; */
									margin-inline: auto;
								}

								@include respond-min($break-desktop-min) {
									/* margin-top: 60px; */
									/* margin-bottom: 60px; */
									margin-block: 60px;
								}

							}
						

logical properties

css-tricks.com

margin-[ inline|block ]


							.article__item {

								margin: 30px 20px;

								@include respond-min($break-tablet-min) {
									margin-inline: auto;
								}

								@include respond-min($break-desktop-min) {
									margin-block: 60px;
								}

							}
						

							.u-apart {
								margin-block: var(--z-ds-space-xxl);
							}
						

Sonstiges

CLS

CLS verhindern


						.image-container {
							height: 0;
							overflow: hidden;
							padding-bottom: 56.25%;
							position: relative;
							img {
								height: 100%;
								left: 0;
								position: absolute;
								top: 0;
								width: 100%;
							}
						}
						

CLS verhindern


						img {
							aspect-ratio: 16/9
						}
						

calc()


						margin-inline: calc(-1 * var(--z-ds-space-xxl));
						

calc()


						/* Need static width for animation.
						   Is calculated with character count, icon width, flexbox gap plus padding
						            text + icon width + flexbox gap + padding-left + padding-right */
						/* prettier-ignore */

						max-width: calc(
							12ch + var(--audio-player-icon-size) + var(--z-ds-space-xs) + 2 * var(--audio-player-padding-inline)
						);
						

Element nach x Zeilen abschneiden


							.teaser-liveblog__text {
								line-height: 1.5;
								max-height: calc(1.5 * 3em);
								overflow: hidden;
							}
							

Element nach x Zeilen abschneiden


							.teaser-liveblog__text {
								-webkit-box-orient: vertical;
								-webkit-line-clamp: 3;
								display: -webkit-box;
								overflow: hidden;
							}
							

Viewport Units

Viewport Units


							.header-fullwidth__media-container {
								height: 60vh;
							}

							.split-header > * {
								width: 50vw;
							}
						

für Größenangaben

Viewport Units für Schriften

Demo Time: codepen.io

Container Queries


							@supports (container-type: inline-size) {
								@container audio-actions (width < 520px) {
									.audio-player[data-state="playing"] {
										~ *:not(.bookmark) {
											display: none;
										}
									}
								}
							}
							

has()


						// extra space after last standard teaser
						.cp-region:has(.cp-area--standard:last-child > .zon-teaser--standard:last-child) {
							margin-bottom: var(--z-ds-space-xxl);
						

						.cp-region:has(.cp-area[hidden]:only-child) {
							margin-block: 0;
						

						// edge case for long title with wrapped topic links and no subtitle
						&:not(:has(.headed-meta__kicker)) {
							row-gap: var(--z-ds-space-l);
						

where()


							<div class="zon-teaser-actions">
								<button class="z-text-button">Podcast abonnieren</button>
							</div>
						

						.z-text-button {
							color: black;
							&:hover {
								color: red;
							}
						}

						.zon-teaser__actions .z-text-button {
							color: inherit;
						}
						

						// keep specificity low to preserve hover state color
						.zon-teaser__actions :where(.z-text-button) {
							color: inherit;
						}
						

CSS ❤️