logo
Enough CSS to get started

/** in your CSS **/
@import 'https://unpkg.com/nokss';

zipped size

nokss is a drop-in stylesheet that works out of the box. No need to use CSS classes: just write plain, semantic HTML, and nokss will style it for you (as much as it can). nokss is designed for prototyping, blogs, articles, small projects, and as a starting point for more invloved applications.

Except a few specific elements, this document is styled with nokss itself, and acts as a preview. You can also change the theme in Theming section to see how it would look with other themes.

Installation

Import in your CSS:

@import 'https://unpkg.com/nokss';

Or link in your HTML:

<link rel="stylesheet"
    href="https://unpkg.com/nokss" />

You can use other CDNs as well:

You can also self-host the stylesheet by downloading it. If you have a bundler that handles CSS imports, install the library from NPM and then load the locally installed version:

npm install nokss
@import 'node_modules/nokss/dist/nokss.css';

Bundles

If you need smaller stylesheets, use one of the following, smaller, partial bundles:

Partial Bundles
Name Size URL Description
Markdown md bundle size
https://unpkg.com/nokss/dist/bundles/md.css
Styles standard markdown elements.
Basic basic bundle size
https://unpkg.com/nokss/dist/bundles/basic.css
Markdown plus buttons.
Slick slick bundle size
https://unpkg.com/nokss/dist/bundles/slick.css
Basic plus general layouts.
Formal formal bundle size
https://unpkg.com/nokss/dist/bundles/formal.css
Basic plus form elements.
Slick Formal slick formal bundle size
https://unpkg.com/nokss/dist/bundles/slick-formal.css
Slick plus form elements.
Size calculated by zipping with Brotli

About Bundles

Toolbars, modals, feeds and cards are not included in any of the partial bundles. They are only available in the full bundle. Also make sure to use one bundle at a time, otherwise you'll end up with duplicate styles.

Usage

Just load the stylesheet. nokss styles elements based on their semantics, so you don't have to worry about class names. The more semantic and accessible your markup is, the more nokss will be able to do for you.

Semantics are mainly dictated by HTML tags and ARIA roles, with HTML and ARIA attributes providing further context. Structural conventions are used for context about more complex semantics:


<a role="button"> ... </a>

☝️ This anchor is styled like a button.


<menu role="toolbar" aria-orientation="vertical">...</menu>

☝️ This menu is styled as a vertical toolbar.


<input type="email" />
<span role="alert">Please enter a valid email</span>

☝️ The <span/> is treated as the error message for the input.


<button role="switch">
  <span>Off</span>
  <span>On</span>
</button>

☝️ The first <span/> is shown in off state, the second in on state.


You can find a comprehensive list of elements and components supported by nokss, how their usage looks like, and their respective customization options, in the following sections:

Theming

nokss exposes CSS variables for customizing the look and feel of your pages. Change the main theme values in the code below and see their effects on the page:



:root {
  --primary-text-color: #fff;
  --primary-color: #1f6feb;
  --roundness: 5px;
}

@media (prefers-color-scheme: light) {
  :root {
    --background-color: #fffdf9;
    --text-color: #393e46;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #0d1118;
    --text-color: #fffcf3;
  }
}

Lorem ipsum

dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Theme's CSS Variables
Variable Default Description
--primary-color #1f6feb The primary color for highlighted text and actions.
--primary-text-color #fff The color of the text that is to be used on backgrounds with primary color.
--text-color #393e46 (light)
#fffcf3 (dark)
Color of text.
--background-color #fffdf9 (light)
#0d1118 (dark)
Color of background.
--danger-color #ff2626 Denotes potentially harmful actions, or error states.
--danger-text-color #fff Denotes potentially harmful actions, or error states.
--roundness 5px Determines how round various elements are (e.g. border radius).
--border-expression 0.05 Determines how visible borders are, 0 being invisible and 1 being fully visible.
--spacing 8px

Specifies the spacing between various elements. It is highly recommended to modify spacing per element instead of tweaking the global version.

Besides these global values, each element also relies on its own specific variables, which are by default calculated from these global values, and can be overriden for customizing the look of individual elements.

Typography

nokss defaults to a native font stack for performance, available in --font-family CSS variable. Font size is based off of rem unit to support browser's default font size. Typography is controlled via the following CSS variables and can be tweaked accordingly:

Typography CSS variables
Variable Default Description
--font-family

-apple-system,
blinkmacsystemfont,
'Segoe UI',
'Noto Sans',
helvetica,
arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji'

Base font family
--font-size 1rem Base font size
--small-font-size 0.833rem Font size for smaller items.
--font-weight 300 Base font weight
--line-height calc(var(--font-size) * 1.5) Base line height

In most cases you don't need to use these variables, as rem unit is itself relative to font size of root.


Interactions

The following CSS variables generally determine various interactive aspects of the design:

Interactions' CSS Variables
Variable Default Description
--interactable-size 32px Size of interactable elements.
--disabled-opacity 0.35 Opacity of disabled elements.
--active-opacity 0.85 Opacity of active elements.
--interactable-brightness 0.96 (light)
2.25 (dark)
Brightness of interactable elements.
--hover-brightness calc(1 - (1 -
 var(--interactable-brightness))
  * 1/10)
(light)
calc(
 var(--interactable-brightness)
  * 1.35)
(dark)
Brightness of interactable elements on hover.

On touch devices, a larger --interactable-size is used:

@media (hover: none) and (pointer: coarse) {
  :root {
    --interactable-size: 40px;
  }
}

Controls

In the following sections you can find details on control elements (buttons, links, toolbars, etc.) that nokss supports, alongside example usage and customization guidelines for each.

Buttons

Use <button>, <a role="button"> or <input type="submit"> to create buttons:

<button>Button</button>
<a role="button">Anchor</a>
Anchor
<input type="submit" value="Submit" />

Button Groups

Group buttons to give all but the last one a secondary actions style:

<menu role="group">
  <button>Secondary</button>
  <button>Primary</button>
</menu>

Use align attribute to align buttons in a menu to the left or right:

<menu role="group" align="right">
  <button>Secondary</button>
  <button>Primary</button>
</menu>

Since align is unfortunately obsolete, for grouping menus you can use CSS with text-align instead:

<menu role="group" style="text-align: right">
...
</menu>

Icon Buttons

Use aria-label to style buttons as icon buttons:

<button aria-label="send"></button>

Using Icon Fonts

Here, an icon font is being used for the icon to be displayed. However, using icon fonts MUST BE done with a lot of care, as they mostly cause accessibility issues. Use an icon font that falls back to characters browsers can display naturally even if the font is not loaded (or is changed), without scrambling the layout.

For this example, and this document in general, graphis icon font is used, which uses similar looking emojis as fallback.


Disabled Buttons

Buttons having disabled attribute will be styled as disabled:

<button disabled>Disabled</button>

Dangerous Buttons

Denote a dangerous action by starting aria-description of the button with "danger" (or "Danger"):

<button aria-description="dangerously removes the file">Remove</button>
<menu role="group">
  <button aria-description="Danger: deletes the draft">
    Delete Draft
  </button>
  <button>Publish</button>
</menu>

Status Badges in Buttons

Use role="status" to add a status badge (such as a counter) to a button:

<button>
  Star <span role="status">3</span>
</button>
<menu role="group">
  <button>
    Watch <span role="status">450k+</span>
  </button>
  <button>
    Fork <span role="status">32</span>
  </button>
  <button aria-description="danger">
    Delete <span role="status"></span>
  </button>
</menu>

Keyboard Shortcuts

Use <kbd> elements inside a button to show keyboard shortcuts:

<menu role="group">
  <button>
    <kbd>Esc</kbd><span>Cancel</span>
  </button>
  <button>
    <span>Accept</span><kbd></kbd>
  </button>
</menu>

Make sure you wrap the label of your button in a separate element so it can be properly spaced.


Customization

Tweak global CSS variables for customizing buttons. If you need further customization, you can use following CSS variables:

Button CSS Variables
Variable Default Description
--button-height var(--interactable-size) Height of the button
--button-min-width calc(var(--button-height) * 3) Minimum width of the button
--button-padding 0 var(--spacing) Padding of the button
--primary-button
 -hover-brightness
1.2 Brightness of primary buttons when hovered.
--primary-button
 -border-brightness
0.9 (light)
1.2 (dark)

Brightness of border of primary buttons (relative to their background).

--secondary-button
 -active-opacity
calc(1 - (1 -
 var(--active-opacity))
  * 2)
(light)
var(--active-opacity) (dark)
Opacity of secondary buttons when active.

For example, you can create a slimmer rounded button by setting the following variables:

button {
  --button-height: calc(var(--interactable-size) * 0.75);
  --roundness: var(--button-height);
}

Or you can create a .fab class for floating action buttons:

.fab {
  --button-height: calc(var(--interactable-size) * 1.5);
  --button-min-width: var(--button-height);
  --roundness: 50%;
}
<button class="fab">🚀</button>

Toolbars

Use role="toolbar" to group some buttons together and display them like a toolbar:

<menu role="toolbar">
  <button aria-label="edit">✏️</button>
  <button aria-label="like">❤️</button>
  <button>Longer</button>
</menu>

Use aria-orientation="vertical" to display a vertical toolbar:

<div style="display: flex; gap: var(--spacing)">
  <menu role="toolbar" aria-orientation="vertical">
    <button aria-label="done"></button>
    <button aria-label="delete">🗑</button>
    <button aria-label="share"></button>
  </menu>
  <p>
    Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur ...
  </p>
</div>

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur ...


You can combine vertical toolbars with <details> element to create drop-in menus:

<details>
  <summary role="button">
    Options ▾
  </summary>
  <div>
    <menu role="toolbar" aria-orientation="vertical">
      <button>Do this</button>
      <button>Or do that</button>
    </menu>
  </div>
</details>
Options ▾

Or even as part of a composite button:

<menu role="toolbar">
  <button>Something</button>
  <details>
    <summary role="button" aria-label="more"></summary>
    <div>
      <menu role="toolbar" aria-orientation="vertical">
        <button>Hellow</button>
        <button>World</button>
        <button>Goodbye Blue Sky</button>
      </menu>
    </div>
  </details>
</menu>

Usage Note

Make sure to use role="button" on the <summary> element. Also make sure to wrap the toolbar in a <div> element so that it can be positioned correctly.

Customization

Tweak global CSS variables for customizing toolbars. If you need further customization, you can use following CSS variables:

Variable Default Description
--toolbar-spacer-gap calc(var(--spacing) * 4) Gap between toolbar buttons
--toolbar-spacer-margin calc(var(--spacing) / 2) Margin around toolbar spacers

Tab Lists

Use role="tablist" to create tab lists:

<nav role="tablist">
  <button role="tab" aria-selected="true">First</button>
  <button role="tab">Second</button>
  <button role="tab">Third</button>
</nav>

Use anchors (<a role="tab">) instead of buttons for more link-like tab lists:

<nav role="tablist">
  <a role="tab" aria-selected="true">First</a>
  <a role="tab">Second</a>
  <a role="tab">Third</a>
</nav>


The behavior for tab lists needs to be added with JavaScript. Use aria-selected="true" to mark a selected element:

document.querySelectorAll(
  '[role="tablist"]>:is(button, [role="tab"]]'
).forEach(button => {
  button.addEventListener('click', () => {
    /* Make sure only one item is selected at a time */
    button.parentNode.querySelectorAll(':is(button, [role="tab"])')
      .forEach(btn => btn.setAttribute('aria-selected', false))
    button.setAttribute('aria-selected', true)
  })
})

You can also use role="status" within tab panels:

<nav role="tablist">
  <a role="tab" aria-selected="true">Friends <span role="status">17</span></a>
  <a role="tab">Follwers <span role="status">32</span></a>
</nav>

Switches

Use role="switch" on buttons to turn them into switches that can be turned on or off:

<button role="switch" aria-checked="false">
  <span>Inactive</span>
  <span>Active</span>
</button>

A switch must have either one or two child elements, the first one acting as the label for inactive state, the second one for the active state.

<menu role="toolbar">
  <button role="switch" aria-checked="false" aria-label="camera">
    <span>🎦</span>
    <span>🎥</span>
  </button>
  <button role="switch" aria-checked="true" aria-label="microphone">
    <span>🎤</span>
    <span>🎙</span>
  </button>
  <button aria-label="raise hand">🤚</button>
  <button aria-label="end call" aria-description="dangerously end call">
    📵
  </button>
</menu>

Switches can also denote dangerous states by adding an aria-description attribute starting with "danger" (or "Danger"):

<button role="switch"
  aria-checked="false"
  aria-description="danger: turns on expert mode.">
  <span>Expert Mode: Off</span>
  <span>Expert Mode: On</span>
</button>

The behavior of switches needs to be added with JavaScript. Use aria-checked="true" to turn the switch on or off:

document.querySelectorAll('[role="switch"]').forEach(sw => {
  sw.addEventListener('click', () => {
    sw.setAttribute(
      'aria-checked',
      sw.getAttribute('aria-checked') === 'true' ? 'false' : 'true'
    )
  }) 
})

Inputs

In the following sections you can find details on input elements (text, radio, range, etc.) that nokss supports, alongside example usage and customization guidelines for each.

Text Input

Use the input tag (with proper type) to create styled inputs:

<input type="email" placeholder="your email"/>
<input type="password" placeholder="your password"/>
<menu role="group" align="end">
  <button>Forgot Password</button>
  <button>Login</button>
</menu>

Use textarea tag for larger inputs:

<textarea placeholder="Write something ..."></textarea>
<menu role="group" align="end">
  <button>Save Draft</button>
  <button>Publish</button>
</menu>

You can use inputs inside horizontal toolbars:

<menu role="toolbar">
  <input type="search" placeholder="Search something ..." />
  <button aria-label="search">🔍</button>
</menu>

Use attributes and ARIA attributes to determine state of the input:

<input type="text" required placeholder="Required">
<input type="text" aria-invalid="true" placeholder="Invalid"/>
<input type="text" disabled placeholder="Disabled"/>

Invalid Inputs

You can always force invalid style for an input by setting the aria-invalid attribute to true. This is specifically useful for required inputs, which are not automatically styled as invalid when empty (since CSS cannot track whether the input is touched or not).


Customization

Tweak global CSS variables for customizing inputs. If you need further customization, you can use following CSS variables:

Input CSS Variables
Variable Default Description
--text-area-
 -min-height
calc(2 *
 var(--spacing) +
 2 * var(--interactable-size))
Minimum height of text area
--input-
 -idle-border-
 -color
var(--background-color) Border color of input when idle
--input-
 -placeholder-
 -expression
0.4 (light)
0.15 (dark)
Expression for calculating placeholder color
--input-
 -background-
 -brightness
calc(1 - (1 -
 var(--interactable-
 -brightness)) * 1.5)
(light)
var(--interactable-
 -brightness)
(dark)
Brightness of input background
--input-
 -background-
 -brightness-
 -focus-
 -dropoff
0.65 (light)
0.25 (dark)

Dropoff of input background brightness when focused

Radio & Checkboxes

Use <input type="radio"> when users have to pick a choice:

<input type="radio" name="sauce" id="mayo"/>
<label for="mayo">Mayo</label>
<br>
<input type="radio" name="sauce" id="ketchup"/>
<label for="ketchup">Ketchup</label>


Use <input type="checkbox"> when users can independently select and deselect some chocies:

<input type="checkbox" id="step1"/>
<label for="step1">Step 1: Do these stuff</label>
<br>
<input type="checkbox" id="step2"/>
<label for="step2">Step 2: Do that stuff</label>
<br>
<input type="checkbox" id="step3" aria-invalid="true"/>
<label for="step3">Step3: Also you MUST accept this.</label>
<br>
<input type="checkbox" id="final" />
<label for="step3">Everything is done.</label>

Use role="switch" on an <input type="checkbox" /> to get a switch-style checkbox:

<label>
  <input type="checkbox" role="switch"/>
  <span>Enable Stuff</span>
</label>

Use role="radiogroup" instead of radio inputs for a different style:

<menu role="radiogroup">
  <button role="radio">Alliance</button>
  <button role="radio">Horde</button>
  <button role="radio">Panda</button>
</menu>

Note that the behavior needs to be added in when using radio groups:

document.querySelectorAll('[role="radiogroup"]').forEach(group => {
  group.querySelectorAll('button, [role="radio"]').forEach(radio => {
    radio.addEventListener('click', () => {
      group.querySelectorAll('button, [role="radio"]').forEach(r => r.setAttribute('aria-checked', false))
      radio.setAttribute('aria-checked', true)
    })
  })
})

Range Input

Use <input type="range"> to create ranges and sliders:

<input type="range" aria-label="some range"/>

Range Input Support

Browsers behave differently in how they allow customization of range inputs. This means that the appearance of your range inputs will vary from browser to browser.


You can use range inputs can in toolbars:

<menu role="toolbar">
  <button aria-label="play/pause" role="switch" aria-checked="false">
    <span></span>
    <span></span>
  </button>
  <button aria-label="stop"></button>
  <input type="range" aria-label="timeline"
    style="--range-input-thumb-width: 16px"/>
  <button>Subtitle</button>
</menu>

Using range inputs in vertical toolbars requires a little bit of a work around:

<menu role="toolbar" aria-orientation="vertical">
  <div class="--vertical-range-container">
    <input type="range" aria-label="volume"/>
  </div>
  <button aria-label="mute" role="switch" aria-checked="false">
    <span>🔇</span>
  </button>
</menu>

The length of the track, in this case, is specified by --track-length CSS variable, which must be set on the container:

.--vertical-range-container {
  --track-length: 256px;
}

Customization

Tweak global CSS variables for customizing range inputs. If you need further customization, you can use following CSS variables:

Range Input CSS Variables
Variable Default Description
--range-input
 -track-height
4px Height of the track
--range-input
 -thumb-width
var(--button-height) Width of the thumb
--range-input
 -thumb-height
calc(
 var(--button-height)
  * 2 / 3)
Height of the thumb
--range-input
 -margin
calc(
 var(--range-input
  -thumb-height) +
  var(--spacing)) 0
Margin of the range input
--range-input
 -empty-track-inversion
0.12 How much to invert the empty track color

For example, you can create a range input with a smaller, round thumb by setting the following variables:

input[type='range'] {
  --range-input-thumb-width: 1.25rem;
  --range-input-thumb-height: 1.25rem;
  --roundness: 1.25rem;
}

Other Input Types

Other input types are styled roughly similar to text inputs, and can similarly be used in toolbars as well:

<menu role="toolbar">
  <select>
    <option>To Be</option>
    <option>Or not to be</option>
  </select>
  <input type="color" aria-label="background"/>
  <input type="color" value="#4223e9" aria-label="foreground"/>
</menu>

<menu role="toolbar">
  <input type="date" value="2023-01-08" aria-label="event date"/>
  <input type="time" value="16:20" aria-label="event time"/>
</menu>

You can use color inputs can in vertical toolbars as well:

<menu role="toolbar" aria-orientation="vertical">
  <input type="color" value="#FF7B54" aria-label="primary color"/>
  <input type="color" value="#FFB26B" aria-label="secodnary color"/>
  <input type="color" value="#FFD56F" aria-label="tertiary color"/>
  <input type="color" value="#939B62" aria-label="accessory color"/>
  <button aria-label="pick" class="icon"></button>
</menu>

Use <input type="file"> for uploading files:

<input type="file" aria-label="profile picture"/>

Field Sets & Grouping

Use field sets to group some form controls together:

<fieldset>
  <legend>Login</legend>
  <input type="email" placeholder="Email address" />
  <input type="password" placeholder="Password" />
  <menu role="group" align="end">
    <button>Cancel</button>
    <button>Login</button>
  </menu>
</fieldset>
Login

Labels

Use inputs within labels to bind them together:

<label>
  Name: <input type="text" />
</label>
<label>
  Phone Number: <input type="tel" />
</label>
<label>
  Bio:<br> <textarea></textarea>
</label>
<label>
  Account privacy:
  <menu role="radiogroup">
    <button role="radio">Public</button>
    <button role="radio">Private</button>
  </menu>
</label>

You can also disassociate labels and inputs:

<label>Name:</label>
<input type="text" placeholder="First name and last name ..." />
<label>Phone Number:</label>
<input type="tel" placeholder="+xx-xxx-xxxxxxx" />

For accessibility, you should always link labels and inputs, either by grouping them in the same label element, by linking them via for and id attributes, or by using the aria-labelledby attribute.


For checkboxes and radio inputs, make sure you wrap the content of the label inside another element (such as a <span/>):

<label>
  <input type="checkbox" />
  <span>Remember me</span>
</label>

Input Status

Use alert or status roles, or ARIA live regions, to notify the user about the status of the input (or give them some hints on how to complete it properly):

<textarea placeholder="Type something ..." maxlength="50" minlength="10"></textarea>
<div align="end" role="status">0/50</div>

You need to add the behavior separately with JavaScript:

textarea.addEventListener('input', () => {
  status.textContent = textarea.value.length + '/' + textarea.maxLength;
})

Alerts (role="alert"), or assertive ARIA live regions (aria-live="assertive") are only displayed when the input is not focused and is invalid:

Enter a valid email address.
Enter a valid email address.
<!-- Using alert: -->
<label>Using alert:</label>
<input type="email" placeholder="Enter an email address" value="invalid@email."/>
<span role="alert">Enter a valid email address.</span>

<!-- Using status: -->
<label>Using status:</label>
<input type="email" placeholder="Enter an email address" value="invalid@email."/>
<span role="status">Enter a valid email address.</span>

You can also use status messages within labels:

Layout

In following sections you can find details on layout elements (containers, grids, etc.) that nokss supports, alongside example usage and customization guidelines for each.

Main Content & Sections

Wrap your main content in <main> tag:

<main>
  <h1>My Main Content</h1>
  <p>My main content goes here.</p>
</main>

Main content is centered with a relative width by default, which falls back to full width on smaller screens, with a small margin. You can configure these via the following CSS variables:

Main Content CSS Variables
Variable Default Description
--main-content
 -max-width
60vw Width of main content
768px Screens smaller than 768px
--main-content
 -margin
calc(var(--spacing) * 4) auto Margin of main content
calc(var(--spacing) * 2) Screens smaller than 768px

Sections have additional vertical spacing between them. Additionally, if a section starts with a linkable header, additional offset will be created so that when loading the page with a hash in the URL, the header has enough spacing from the top of the screen (this is set to half the distance to previous section).

Section CSS Variables
Variable Default Description
--section
 -spacing
calc(var(--spacing) * 16) Spacing between sections
--section
 -header-offset
calc(var(--section-spacing) / 2) Offset of section header

Use <header> and <footer> elements directly in the <body> element to create a header and footer for the entire page:

<body>
  <header>
    Header stuff
  </header>
  <main>
    ...
  </main>
  <footer>
    Footer stuff
  </footer>
</body>

Main header and footer are sticky by default, which can be overriden using CSS:

body > footer {
  position: initial;
}

You can customize main header and footer using the following CSS variables:

Footer CSS Variables
Variable Default Description
--footer-blur 16px Backdrop blur of the footer
--footer-brightness var(--sidebar-brightness) Brightness of the footer (relative to the background)
--footer-background var(--background-color) Background of the footer
--footer-background
 -opacity
0.65 Background opacity of the footer
--footer-padding var(--spacing)
calc(2 * var(--spacing))
Padding of footer content
Header CSS Variables
Variable Default Description
--header-blur var(--footer-blur) Backdrop blur of the header
--header-brightness var(--footer-brightness) Brightness of the header (relative to the background)
--header-background var(--footer-background) Background of the header
--header-background
 -opacity
var(--footer-background
 -opacity)
Background opacity of the header
--header-padding var(--footer-padding) Padding of header content

Feed & Cards

Use <article> elements in a container with the feed role to create cards:

<section role="feed">
  <article>First thingy</article>
  <article>Second thingy</article>
  <article>Third thingy</article>
</section>
First thingy
Second thingy
Third thingy

Use images directly in a card to display them as card banner:

<section role="feed">
  <article>
    <img src="https://picsum.photos/800/300" alt="random image"/>
    <h2>Card Title</h2>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
      Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
    </p>
    <footer align="end">
      <div>
        <button>Learn More</button>
      </div>
    </footer>
  </article>
</section>

Use <header> to create a card header:

<section role="feed">
  <article>
    <header>
      <h2>Card Title</h2>
    </header>
    <img src="https://picsum.photos/800/300" alt="random image"/>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </p>
    <footer align="end">
      <div>
        <button>Learn More</button>
      </div>
    </footer>
  </article>
</section>

Use <address> for attributing the card to some author:

<section role="feed">
  <article>
    <header>
      <address>
        <img src="https://some.avatar" alt="random avatar"/>
        <div>
          John Doe<br>
          <small>Writes about stuff</small>
        </div>
      </address>
    </header>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </p>
    <footer>
      <div role="toolbar" align="end">
        <button aria-label="like"></button>
        <button aria-label="comment">💬</button>
        <button aria-label="share"></button>
      </div>
    </footer>
  </article>
</section>

Note how in the above example, the <footer> is used, with a toolbar within it, to provide a set of actions for the card. You can also just give the footer element the toolbar role:

<section role="feed">
  <article role="comment">
    <header>
      <address>
        <img src="https://randomuser.me/api/portraits/women/95.jpg" loading="lazy" alt="random avatar"/>
        Jeanette Martin
      </address>
    </header>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </p>
    <footer role="toolbar">
      <button aria-label="like" >❤ 33k</button>
      <button aria-label="comment">💬 1.2k</button>
      <button aria-label="repost">🔁 1,024</button>
      <button aria-label="share"></button>
    </footer>
  </article>
</section>

Put a toolbar at the beginning of the card to provide top-side actions:

<section role="feed">
  <article>
    <menu role="toolbar">
      <button aria-label="like" class="icon"></button>
      <button aria-label="comment" class="icon">💬</button>
      <button aria-label="share" class="icon"></button>
    </menu>
    <img src="https://picsum.photos/800/450" alt="random image"/>
    <address>
      <img src="https://some.avatar" alt="random avatar"/>
      Jane Smith
    </address>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </p>
  </article>
</section>

Alternatively, you can put the toolbar in the header:

<section role="feed">
  <article>
    <header>
      <address>
        <img src="https://some.avatar" alt="random avatar"/>
        <div>
          Max Mustermann <br>
          <small>General Enthusiast</small>
        </div>
      </address>
      <menu role="toolbar">
        <details>
          <summary role="button" aria-label="more"></summary>
          <div>
            <menu role="toolbar" aria-orientation="vertical">
              <button>Share</button>
              <button>Remove</button>
              <button>Report</button>
            </menu>
          </div>
        </details>
      </menu>
    </header>
    <img src="https://picsum.photos/800/400" alt="random image"/>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
    </p>
  </article>
</section>

Set aria-orientation attribute on the feed to indicate the direction of the feed:

<section role="feed" aria-orientation="horizontal">
  <article>
      <img src="https://picsum.photos/400" alt="random image"/>
      <h3>Lorem Ipsum</h3>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p>
      <footer>
        <button>Learn More</button>
      </footer>
  </article>
  <article>...</article>
  <article>...</article>
  <article>...</article>
</section>
random image

Lorem Ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit

random image

Eiusmod Tempor

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

random image

Consectetur

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

random image

Magna Aliqua

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.


Use aria-busy attribute to indicate that the feed is loading:

<section role="feed" aria-busy="true">
  ...
</section>
random avatar
Erica Parker
W 6th St

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

random avatar
Kelly Davis
Belt Line Rd

Ut enim ad minim veniam, quis nostrud exercitation ullamco ...

random avatar
Aaron Lambert
Groveland Terrace

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.



Customization

Tweak global CSS variables for customizing modals. If you need further customization, you can use following CSS variables:

Feed CSS variables
Variable Default Description
--card-shadow 0 1px 3px 0 rgba(0 0 0 / 10%) (light)
0 2px 6px 0 rgba(0 0 0 / 50%) (dark)
Shadow of a card.
--card-brightness 1 (light)
calc(1 - (1 -
 var(--interactable-brightness)
) * 0.5)
(dark)

Brightness card's background (relative to background).

--raised-card
 -transform
translateY(-2px) Transformation of a raised card.
--raised-card
 -shadow
0 3px 9px 0 rgba(0 0 0 / 15%) (light)
0 6px 18px 0 rgba(0 0 0 / 55%) (dark)
Shadow of a raised card.
--raised-card
 -brightness
calc(1 - (1 -
 var(--interactable-brightness)
) * 0.25)
(light)
calc(1 - (1 -
 var(--interactable-brightness)
) * 0.65)
(dark)

Brightness of a raised card's background (relative to background).

--horizontal-feed
 -card-width
256px Width of a card in horizontal feed.

Modals

Use <dialog> element to create and display a modal:

<button onclick="document.querySelector('#modal-1').showModal()">Show Modal</button>
<dialog id="modal-1">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  <footer>
    <form method="dialog" align="end" role="group">
      <button>Whatever</button>
      <button>Got It</button>
    </form>
  </footer>
</dialog>

Lorem ispum

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Make sure you use the .showModal() method for opening the dialog. Checkout MDN's guides to learn more about the <dialog> element.


Use <header> and <footer> elements to add a header and footer to the modal.

<dialog>
  <header><h2>Modal with header</h2></header>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
  <footer>
    <form method="dialog" align="end">
      <button>Close</button>
    </form>
  </footer>
</dialog>

Modal with header

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Set --modal-animation property to change the animation of the modal. This needs to be the name of the animation you want to use. nokss provides some animations out of the box, which you can try in the example below:


Tweak global CSS variables for customizing modals. If you need further customization, you can use following CSS variables:

Modal CSS Variables
Variable Default Description
--modal-width 512px Width of the modal.
--modal
 -backdrop-blur
8px Blurring behind modal's backdrop
--modal
 -backdrop
 -background
rgba(0 0 0 / 50%) Background of modal's backdrop
--modal
 -background
 -brightness
2

Brightness of the modal's background, relative to page's background.

--modal
 -animation
grow-in

The animation to display when a modal is opened. Can be the name of any custom defined animation. See animations section for mor edetails.

Customization Note

Dialog backdrop DOES NOT inherit root scope variables. Override modal related variables for :root AND ::backdrop:

:root,
::backdrop {
  --modal-backdrop-background: rgba(128 128 128 / 25%);
  --modal-backdrop-blur: 32px;
}