Graffiti

github

UI Blocks

Common UI patterns done simply, without 100 classes.

These are common, UI patterns that can be accomplished easily with a couple of classes or a combination of other Graffiti elements. Many other UI libraries make you bring in a react component with endless props options and clutter. Cut that out with real, practical html and css that you can connect to your framework of choice. Clientside or otherwise.

Accordion

Native HTML accordion using details/summary with smooth animations via @starting-style and allow-discrete.

Accordion Variants

Getting Started

Graffiti is a drop-in CSS library that styles native HTML elements automatically. No classes required for basic styling—just write semantic HTML.

Installation Options

Install via npm with npm install graffiti-css, or include directly from a CDN. The library works with any framework or vanilla HTML.

View Source Code

The right-aligned variant places the toggle indicator on the opposite side, useful for "show code" toggles or secondary actions.

Frequently Asked Questions

The minimal variant removes extra padding and uses a simple +/− indicator, perfect for compact FAQ sections or nested accordions.

Code
<div class="stack">
  <!-- Basic details/summary -->
  <details>
    <summary>Getting Started</summary>
    <p>
      Graffiti is a drop-in CSS library that styles native HTML elements automatically.
      No classes required for basic styling—just write semantic HTML.
    </p>
  </details>

  <!-- Bordered variant -->
  <details class="bordered">
    <summary>Installation Options</summary>
    <p>
      Install via npm with <code>npm install graffiti-css</code>, or include directly
      from a CDN. The library works with any framework or vanilla HTML.
    </p>
  </details>

  <!-- Right-aligned trigger variant -->
  <details class="right">
    <summary>View Source Code</summary>
    <p>
      The right-aligned variant places the toggle indicator on the opposite side,
      useful for "show code" toggles or secondary actions.
    </p>
  </details>

  <!-- Minimal variant with +/− toggle -->
  <details class="minimal">
    <summary>Frequently Asked Questions</summary>
    <p>
      The minimal variant removes extra padding and uses a simple +/− indicator,
      perfect for compact FAQ sections or nested accordions.
    </p>
  </details>
</div>

Breadcrumbs

Simple breadcrumb navigation with slash separators. Uses semantic HTML with proper ARIA attributes for accessibility.

Breadcrumbs

Code
<div class="stack">
	<!-- Basic breadcrumb trail -->
	<nav class="breadcrumbs" aria-label="Breadcrumb">
		<ul class="no-list">
			<li><a href="/">Home</a></li>
			<li><a href="/products">Products</a></li>
			<li><a href="/products/electronics">Electronics</a></li>
			<li aria-current="page">Wireless Headphones</li>
		</ul>
	</nav>

	<!-- Custom separator -->
	<nav class="breadcrumbs" style="--separator: '›'" aria-label="Breadcrumb">
		<ul class="no-list">
			<li><a href="/">Home</a></li>
			<li><a href="/docs">Docs</a></li>
			<li aria-current="page">Getting Started</li>
		</ul>
	</nav>
</div>

Dropdown Menu

Native dropdown menu using HTML popover API and CSS anchor positioning. No JavaScript required for open/close.

Dropdown Menu

Code
<div class="cluster">
  <!-- Basic dropdown with header, items, and dividers -->
  <div class="dropdown" style="--anchor: --dropdown-options">
    <button popovertarget="dropdown-options">Options</button>
    <div id="dropdown-options" popover class="dropdown-menu">
      <div class="dropdown-header">Account</div>
      <hr>
      <a href="#profile">Profile</a>
      <a href="#settings">Settings</a>
      <hr>
      <button>Sign Out</button>
    </div>
  </div>

  <!-- End-aligned dropdown -->
  <div class="dropdown end" style="--anchor: --dropdown-actions">
    <button popovertarget="dropdown-actions">Actions</button>
    <div id="dropdown-actions" popover class="dropdown-menu">
      <div class="dropdown-header">Quick Actions</div>
      <hr>
      <a href="#edit">Edit</a>
      <a href="#duplicate">Duplicate</a>
      <a href="#archive">Archive</a>
      <hr>
      <button>Delete</button>
    </div>
  </div>
</div>

Input Group

Input field with connected button - commonly used for copy-to-clipboard, search, or form submission patterns. Works with all button variants.

Input Group

Code
<div class="stack">
  <!-- Copy URL example -->
  <div class="input-group">
    <input type="text" value="https://example.com/share/abc123" readonly />
    <button>Copy</button>
  </div>

  <!-- Search example with primary button -->
  <div class="input-group">
    <input type="search" placeholder="Search documentation..." />
    <button class="primary">Search</button>
  </div>

  <!-- Mini variant example -->
  <div class="input-group">
    <input type="text" placeholder="Enter code..." />
    <button class="mini">Apply</button>
  </div>
</div>

Sidebar Navigation

Vertical navigation for app sidebars with collapsible sections. Uses native details/summary for expand/collapse. Add .active class to show current page.

Sidebar Navigation

Code
<nav class="sidebar-nav">
  <a href="#inbox">
    <svg viewBox="0 0 20 20" fill="currentColor">
      <path
        d="M3 5a2 2 0 012-2h10a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5zm2 0v3h10V5H5zm0 5v5h10v-5H5z"
      />
    </svg>
    Inbox
  </a>

  <a href="#issues">
    <svg viewBox="0 0 20 20" fill="currentColor">
      <circle
        cx="10"
        cy="10"
        r="7"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
      />
    </svg>
    Issues
  </a>

  <details open>
    <summary>
      <svg viewBox="0 0 20 20" fill="currentColor">
        <path d="M10 2a8 8 0 100 16 8 8 0 000-16zM5 10a5 5 0 1110 0H5z" />
      </svg>
      Cycles
    </summary>
    <a href="#current">Current</a>
    <a href="#upcoming" class="active">Upcoming</a>
    <a href="#past">Past</a>
  </details>

  <details>
    <summary>
      <svg viewBox="0 0 20 20" fill="currentColor">
        <rect
          x="3"
          y="3"
          width="14"
          height="14"
          rx="2"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        />
        <path
          d="M7 7h6M7 10h6M7 13h4"
          stroke="currentColor"
          stroke-width="1.5"
          stroke-linecap="round"
        />
      </svg>
      Projects
    </summary>
    <a href="#website">Website Redesign</a>
    <a href="#mobile">Mobile App</a>
    <a href="#api">API v2</a>
  </details>

  <a href="#views">
    <svg viewBox="0 0 20 20" fill="currentColor">
      <path d="M3 4h14v2H3V4zm0 5h14v2H3V9zm0 5h14v2H3v-2z" />
    </svg>
    Views
  </a>

  <a href="#settings">
    <svg viewBox="0 0 20 20" fill="currentColor">
      <circle
        cx="10"
        cy="10"
        r="7"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
      />
    </svg>
    Settings
  </a>
</nav>

App Shell Layout

Main Content

The sidebar stays fixed while this content scrolls independently.

Content block 1
Code
<!-- App Shell Layout Demo -->
<!-- Uses .layout-sidebar.fill for sticky full-height sidebar -->
<!-- Uses .split.vertical for sidebar internal layout (nav top, footer bottom) -->

<div class="layout-sidebar fill" style="--layout-gap: 0; height: 400px;">
  <aside class="split vertical box">
    <nav class="sidebar-nav">
      <strong style="padding: var(--pad-s) var(--pad-m);">APP NAME</strong>
      <a href="#home" class="active"> Home </a>
      <a href="#dashboard"> Dashboard </a>
      <details>
        <summary> Settings </summary>
        <a href="#general">General</a>
      </details>
    </nav>
    <div class="stack" style="padding: var(--pad-s) var(--pad-m);">
      <small>v1.0.0</small>
      <button class="mini">Upgrade</button>
    </div>
  </aside>
  <section class="stack" style="padding: var(--pad-l); ">
    <h3>Main Content</h3>
    <p>The sidebar stays fixed while this content scrolls independently.</p>
    <div class="box">Content block 1</div>
  </section>
</div>

.header

Full-width header with navigation

Your Logo

&.border

Header with subtle border on bottom

Your Logo

&.sticky

Header that sticks to the top when scrolling

Sticky Header

.swipe

Interactive swipe component

You can swipe me side to side, no JavaScript. (although you do need JS for events)

&.stop

Swiper that stays in it's "open" state.

You can swipe me side to side, minor JavaScript needed for Safari

You can have multiple buttons of various styles too.

You can swipe me side to side, minor JavaScript needed for Safari

User Menu

User account dropdown combining avatar trigger with dropdown menu. Works with both image and initials avatars.

User Menu

Code
<div class="cluster">
	<!-- User menu with image avatar -->
	<div class="dropdown" style="--anchor: --user-menu-jane">
		<button class="avatar" popovertarget="user-menu-jane">
			<img src="https://i.pravatar.cc/150?u=jane" alt="Jane Doe" />
		</button>
		<div id="user-menu-jane" popover class="dropdown-menu">
			<div class="dropdown-header">
				<strong>Jane Doe</strong>
				<br />
				jane@example.com
			</div>
			<hr />
			<a href="#profile">Profile</a>
			<a href="#settings">Settings</a>
			<hr />
			<button>Sign Out</button>
		</div>
	</div>

	<!-- User menu with initials avatar (end-aligned) -->
	<div class="dropdown end" style="--anchor: --user-menu-john">
		<button class="avatar" popovertarget="user-menu-john">
			JD
		</button>
		<div id="user-menu-john" popover class="dropdown-menu">
			<div class="dropdown-header">
				<strong>John Doe</strong>
				<br />
				john@example.com
			</div>
			<hr />
			<a href="#profile">Profile</a>
			<a href="#settings">Settings</a>
			<hr />
			<button>Sign Out</button>
		</div>
	</div>
</div>