Skip to main content

Smol CSS Layouts

/** Credits Stephanie Eckles
* https://smolcss.dev
*/

/** Responsive CSS Grid (RAM v2) */
.css-grid {
--min: 15ch;
--gap: 1rem;

display: grid;
grid-gap: var(--gap, 1rem);

/* min() with 100% prevents overflow
in extra narrow spaces */
grid-template-columns: repeat(
auto-fit,
minmax(min(100%, var(--min, 15ch)), 1fr)
);
}

/** Responsive Flexbox Grid */
.flexbox-grid {
--min: 10ch;
--gap: 1rem;

display: flex;
flex-wrap: wrap;
gap: var(--gap, 1rem);
}

.flexbox-grid > * {
flex: 1 1 var(--min, 10ch);
}

/** Intrinsic Container */
.container {
--container-max: 60ch;
--margin: 3rem;

inline-size: min(100% - var(--margin, 3rem), var(--container-max, 60ch));
margin-inline: auto;
}

/** Breakout Grid */
.breakout-grid {
--max-content-width: 50ch;
--breakout-difference: 0.2;

/* Compute total allowed grid width to `--breakout-difference`
larger than content area */
--breakout-grid-width: calc(
var(--max-content-width, 50ch) +
(var(--max-content-width, 50ch) * var(--breakout-difference, 0.2))
);

display: grid;
grid-template-columns:
[grid-start] 1fr
[content-start] minmax(min(100%, var(--max-content-width, 50ch)), 1fr)
[content-end] 1fr
[grid-end];
inline-size: min(100% - 2rem, var(--breakout-grid-width));
row-gap: 1rem;
margin-block: 5vb;
margin-inline: auto;
}

.breakout-grid > *:not(.breakout) {
grid-column: content;
}

.breakout-grid > .breakout {
grid-column: grid;
}

/** Responsive Padding */
.responsive-padding {
padding-block: 1.5rem;
padding-inline: clamp(1rem, 5%, 3rem);
}

/** Responsive Sidebar Layout */
.sidebar {
display: grid;
grid-template-columns: fit-content(20ch) minmax(min(50vw, 30ch), 1fr);
}

/** Aspect Ratio Gallery */
.aspect-ratio-gallery {
--min: 15rem;
--aspect-ratio: 4/3;
--gap: 0;
}

.aspect-ratio-gallery li {
block-size: max(25vh, 15rem);
}

@supports (aspect-ratio: 1) {
.aspect-ratio-gallery li {
aspect-ratio: var(--aspect-ratio, 4/3);
block-size: auto;
}
}

.aspect-ratio-gallery img {
display: block;
object-fit: cover;
inline-size: 100%;
block-size: 100%;
}

/** Flexible Unbreakable Boxes */
.unbreakable-box {
--color-light: hsl(261deg 65% 90%);
--color-dark: hsl(261deg 20% 43%);

/* Smol Responsive Padding FTW! */
padding: clamp(0.75rem, 3%, 2rem);
color: var(--color-dark);
font-size: 1.15rem;

/* Help prevent overflow of long words/names/URLs */
word-break: break-word;

/* Optional, not supported for all languages */
hyphens: auto;
background-color: var(--color-light, hsl(261deg 65% 90%));
margin-block: 2rem;
margin-inline: auto;

/* Provide a max-width and prevent overflow */
inline-size: min(50ch, 90%);
}

.unbreakable-box footer {
color: var(--color-light, hsl(261deg 65% 90%));
font-size: 0.9rem;
background-color: var(--color-dark, hsl(261deg 20% 43%));
padding-block: 0.25em;
padding-inline: 0.5em;
margin-block-start: 1rem;

/* Creates a visual box shrunk to `max-content` */
inline-size: fit-content;
}

/** Background Picture */
.background-picture img {
--background-img-brightness: 0.45;
--background-img-saturate: 1.25;

object-fit: cover;
inline-size: 100%;
block-size: 100%;

/* decrease brightness to improve text contrast */
filter: brightness(var(--background-img-brightness, 0.45))
saturate(var(--background-img-saturate, 1.25));
}

/* Necessary if not already within a reset */
.background-picture :is(img, picture) {
display: block;
}

.background-picture-content {
/* ensure stacking context above the picture */
position: relative;
align-self: center;
padding: 1rem;
color: white;
text-align: center;
}

.background-picture-content p {
font-size: clamp(1.35rem, 5vw, 1.75rem);
line-height: 1.3;
}

/** Composable Card Component */
.card-component {
--img-ratio: 3/2;

display: flex;
flex-direction: column;

/* Supported for flexbox in modern browsers since April 2021 */
gap: 1rem;
border-radius: 0.5rem;
box-shadow: 0 0 0.5rem hsl(0deg 0% 0% / 35%);
}

.card-component > img {
aspect-ratio: var(--img-ratio);
object-fit: cover;
inline-size: 100%;
}

.card-component > img:first-child {
border-radius: 0.5rem 0.5rem 0 0;
}

.card-component > img:last-child {
border-radius: 0 0 0.5rem 0.5rem;
margin-block-start: auto;
}

.card-component > :not(img) {
margin-inline: 1rem;

/* Prevent typography "orphans" */
text-wrap: pretty;
}

.card-component > :not(img):first-child {
margin-block-start: 1rem;
}

/* Enhanced `:not()` accepts a selector list,
but as a fallback you can chain `:not()` instead */
.card-component > :last-of-type:not(img, h2, h3, h4) {
margin-block-end: 1rem;
}

.card-component > :not(h2, h3, h4) {
font-size: 0.9rem;
}

.card-component > a {
align-self: start;
}

/** Avatar List Component */
.avatar-list {
--avatar-size: 3rem;
--avatar-count: 3;

display: grid;

/* Default to displaying most of the avatar to
enable easier access on touch devices, ensuring
the WCAG touch target size is met or exceeded */
grid-template-columns: repeat(
var(--avatar-count, 3),
max(44px, calc(var(--avatar-size, 3rem) / 1.15))
);

/* `padding` matches added visual dimensions of
the `box-shadow` to help create a more accurate
computed component size */
padding: 0.08em;
font-size: var(--avatar-size, 3rem);
}

@media (any-hover: hover) and (any-pointer: fine) {
.avatar-list {
/* We create 1 extra cell to enable the computed
width to match the final visual width */
grid-template-columns: repeat(
calc(var(--avatar-count, 3) + 1),
calc(var(--avatar-size, 3rem) / 1.75)
);
}
}

.avatar-list li {
inline-size: var(--avatar-size, 3rem);
block-size: var(--avatar-size, 3rem);
}

.avatar-list li:hover ~ li a,
.avatar-list li:focus-within ~ li a {
transform: translateX(33%);
}

.avatar-list img,
.avatar-list a {
display: block;
border-radius: 50%;
}

.avatar-list a {
transition: transform 180ms ease-in-out;
}

.avatar-list img {
inline-size: 100%;
block-size: 100%;
object-fit: cover;
background-color: white;
box-shadow: 0 0 0 0.05em white, 0 0 0 0.08em rgb(0 0 0 / 15%);
}

.avatar-list a:focus {
outline: 2px solid transparent;

/* Double-layer trick to work for dark and light backgrounds */
box-shadow: 0 0 0 0.08em hsl(221deg 29% 23%), 0 0 0 0.12em white;
}

/** Scroll Snap */
.scroll-snap {
/* Set up container positioning */
display: grid;
grid-auto-flow: column;
grid-gap: 1.5rem;

/* Enable overflow along our scroll axis */
overflow-x: auto;

/* Define axis and scroll type, where
`mandatory` means any scroll attempt will
cause a scroll to the next item */
scroll-snap-type: x mandatory;
padding-block: 0 1.5rem;
padding-inline: 0;
-webkit-overflow-scrolling: touch;
}

.scroll-snap > * {
inline-size: min(45ch, 60vw);

/* Choose how to align children on scroll */
scroll-snap-align: center;

/* Prevents scrolling past more than one child */
scroll-snap-stop: always;
}

/** Focus Style */

/* For "real-world" usage, you do not need to scope
these custom properties */
.focus-styles :is(a, button, input, textarea, summary) {
/* Using max() ensures at least a value of 2px,
while allowing the possibility of scaling
relative to the component */
--outline-size: max(2px, 0.08em);
--outline-style: solid;
--outline-color: currentcolor;
}

/* Base :focus styles for fallback purposes */
.focus-styles :is(a, button, input, textarea, summary):focus {
outline: var(--outline-size) var(--outline-style) var(--outline-color);
outline-offset: var(--outline-offset, var(--outline-size));
}

/* Final :focus-visible styles */
.focus-styles :is(a, button, input, textarea):focus-visible {
outline: var(--outline-size) var(--outline-style) var(--outline-color);
outline-offset: var(--outline-offset, var(--outline-size));
}

/* Remove base :focus styles when :focus-visible is available */
.focus-styles :is(a, button, input, textarea):focus:not(:focus-visible) {
outline: none;
}

/* Demonstration of customizing */
.focus-styles li:nth-of-type(2) a {
--outline-style: dashed;
}

.focus-styles input {
--outline-color: red;
}

.focus-styles textarea {
--outline-size: 0.25em;
--outline-style: dotted;
--outline-color: green;
}

.focus-styles li:nth-of-type(4) button {
font-size: 2.5rem;
}

/** Visited Styles */
ul.visited-styles {
--color-background: white;
--color-accent: green;

display: grid;
grid-gap: 0.5rem;
padding: 1rem;
background-color: var(--color-background);
border: 1px solid var(--color-accent);
border-radius: 0.5rem;
inline-size: fit-content;
}

.visited-styles a {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
color: hsl(0deg 0% 13%);
text-decoration-color: var(--color-accent);
text-decoration-style: dotted;
text-underline-offset: 0.15em;
}

.visited-styles a span {
margin-inline-end: 0.25em;

/* Remove from normal document flow
which excludes it from receiving
the underline ✨ */
float: inline-start;
}

.visited-styles a span::after {
color: var(--color-background);
content: '✔';
}

.visited-styles a:visited span::after {
color: var(--color-accent);
}

/** Document Styles */
* {
box-sizing: border-box;
margin: 0;
}

body {
display: flex;
flex-direction: column;
color: hsl(0deg 0% 13%);
font-family: system-ui, sans-serif;
line-height: 1.5;
min-block-size: 100svh;
padding-block: 5vh 1rem;
padding-inline: clamp(1rem, 5vw, 3rem);
}

body > * {
--layout-spacing: max(8vh, 3rem);
--max-width: 70ch;

inline-size: min(100%, var(--max-width));
margin-inline: auto;
}

main {
margin-block-start: var(--layout-spacing);
}

footer {
margin-block-start: auto;
padding-block-start: var(--layout-spacing);
}

footer p {
color: hsl(0deg 0% 46%);
font-size: 0.9rem;
border-block-start: 1px solid hsl(0deg 0% 80%);
padding-block-start: 0.25em;
}

:is(h1, h2, h3) {
line-height: 1.2;
}

:is(h2, h3):not(:first-child) {
margin-block-start: 2em;
}

article * + * {
margin-block-start: 1em;
}

a {
color: navy;
text-underline-offset: 0.15em;
}

/** Transitions */
.transitions > * {
--transition-property: transform;
--transition-duration: 180ms;

transition: var(--transition-property) var(--transition-duration) ease-in-out;
}

.rise:hover > * {
transform: translateY(-25%);
}

.rotate:hover > * {
transform: rotate(15deg);
}

.zoom:hover > * {
transform: scale(1.1);
}

.fade > * {
--transition-property: opacity;
--transition-duration: 500ms;
}

.fade:hover > * {
opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
.transitions > * {
--transition-duration: 0.01ms;
}
}

/** Article Anchor */
.article-anchor {
position: relative;
display: grid;
grid-template-columns: min-content auto;
margin-block-start: 2em;

/* You could pull this property out and
generalize it under the selector `[id]` as
it won't affect flow layout or regular margins */
scroll-margin-top: 2em;
}

.article-anchor:target::before {
position: absolute;
color: currentcolor;
font-size: 0.9rem;
font-style: italic;
content: "Is it me you're looking for?";
inset-block-start: -1.25rem;
inset-inline-start: 0;
}

.article-anchor a {
grid-row-start: 1;
align-self: start;
font-size: 1rem;
line-height: 1;
text-decoration: none;

/* We're using `transform` vs. margins */
transform: translateX(-50%) translateY(25%);

/* Be sure to check that your own colors still meet
or exceed 4.5:1 contrast when using lowering opacity */
opacity: 0.75;
}

.article-anchor a:hover {
text-decoration: underline;
text-underline-offset: 0.25em;
opacity: 1;
}

.article-anchor a:focus {
outline: 2px solid currentcolor;
outline-offset: 0.15em;
}

/* Visually hidden while remaining accessible to
assistive tech like screen readers */
.article-anchor-hidden {
position: absolute;
overflow: hidden;
inline-size: 0;
block-size: 0;
}

/** List Markers */
.list-markers {
--marker-color: hsl(268deg 96% 65%);

padding: 0;
margin-block: 0;
margin-inline: 2em 0;
}

.list-markers li {
padding-inline-start: 0.5em;
}

.list-markers li + li {
margin-block-start: 0.5em;
}

.list-markers li::marker {
color: var(--marker-color);
}

ul.list-markers li::marker {
font-size: 1.15em;
content: attr(data-icon);
}

ol.list-markers li::marker {
font-size: 1.5em;
font-family: cursive;

/* The `list-item` counter is provided by
the browser for lists */
content: counter(list-item);
}