Header
Preview
Features
- Responsive Design: The navigation adapts to various screen sizes such as smartphones, tablets, and PCs.
- Accessibility: Utilizes skip links and
aria-labels
for comfortable operation with screen readers. - Dark Mode Support: Automatically switches between light and dark modes based on system settings.
- Intuitive Hamburger Button: Employs a hamburger icon that allows menus to expand and collapse on small screens like smartphones and tablets.
- Minimal Props: Easily implement rich UI by specifying only
logo
andnavItems
.
Even if the logo
text is long, styles are applied to prevent wrapping or overflow.
Properties
Property Name | Type | Default Value | Description |
---|---|---|---|
logo | string | "LOGO" | The string displayed at the top left of the header. Clicking it returns to the root. |
navItems | {label: string; href: string;}[] | [ { label: "Home", href: "/" } ] | An array of navigation items to display. |
Github
Code Example
import "@/styles/tailwind.css";import { useState } from "react";
type NavItem = { label: string; href: string;};
type HeaderProps = { logo?: string; navItems?: NavItem[];};
export default function Header({ logo = "LOGO", navItems = [ { label: "Home", href: "#" }, { label: "About", href: "#" }, { label: "Contact", href: "#" }, ],}: HeaderProps) { const [isOpen, setIsOpen] = useState(false);
return ( <header className="w-full bg-white text-black dark:bg-black dark:text-white"> <div className="flex items-center justify-between px-4 py-4 md:px-8"> <h1 className="text-xl font-bold overflow-hidden text-ellipsis whitespace-nowrap"> <a href="/" className="transition-colors duration-200 hover:opacity-75 focus:opacity-75" > {logo} </a> </h1> <button onClick={() => setIsOpen(!isOpen)} aria-label="Toggle Menu" type="button" aria-expanded={isOpen} className="inline-flex items-center justify-center p-2 rounded md:hidden hover:bg-gray/10 dark:hover:bg-white/10" > <div className="relative w-6 h-6"> <span className={`absolute block h-[2px] w-6 bg-current transition-transform duration-300 ease-in-out ${ isOpen ? "top-[13px] rotate-45" : "top-[6px]" }`} /> <span className={`absolute block h-[2px] w-6 bg-current transition-opacity duration-300 ease-in-out ${ isOpen ? "opacity-0" : "top-[13px]" }`} /> <span className={`absolute block h-[2px] w-6 bg-current transition-transform duration-300 ease-in-out ${ isOpen ? "bottom-[6px] -rotate-45" : "bottom-[6px]" }`} /> </div> </button> <nav className="hidden md:flex md:items-center md:space-x-8"> {navItems.map(({ label, href }) => ( <a key={href} href={href} className="block px-2 py-1 transition-opacity duration-200 hover:opacity-75 focus:opacity-75" > {label} </a> ))} </nav> </div> <div className={`md:hidden transition-[max-height] duration-300 overflow-hidden ${ isOpen ? "max-h-96 border-t border-gray/30 dark:border-white/30" : "max-h-0" }`} > <ul className="flex flex-col gap-4 py-4 px-4"> {navItems.map(({ label, href }) => ( <li key={href}> <a href={href} className="block px-2 py-1 transition-opacity duration-200 hover:opacity-75 focus:opacity-75" > {label} </a> </li> ))} </ul> </div> </header> );}
Usage
Import the component and incorporate it into your pages or layouts. Override logo
and navItems
as needed.
Adjust the import paths according to each framework, and it’s convenient to manage the above code in separate files.
// import "@/components/Header";import Header from "@/components/Header";
export default function Layout({ children }) { return ( <> <Header logo="MyWebsite" navItems={[ { label: "Home", href: "/" }, { label: "Services", href: "/services" }, { label: "Blog", href: "/blog" }, ]} /> <main id="main">{children}</main> </> );}
import { colorPalette } from "@/styles/ColorPalette";import { typography } from "@/styles/Typography";import { LitElement, css, html } from "lit";import { customElement, property, state } from "lit/decorators.js";
type NavItem = { label: string; href: string;};
@customElement("my-header")export class MyHeader extends LitElement { @property({ type: String }) logo = "LOGO"; @property({ type: Array }) navItems: NavItem[] = [ { label: "Home", href: "/" }, { label: "About", href: "/about" }, { label: "Contact", href: "/contact" }, ];
@state() private isOpen = false;
static styles = [ colorPalette, typography, css` :host { display: block; background-color: var(--light-basic-white); color: var(--light-basic-black); font-family: var(--font-family-sans); } @media (prefers-color-scheme: dark) { :host { background-color: var(--dark-basic-black); color: var(--dark-basic-white); } } .header { width: 100%; } .skip-link { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; } .skip-link:focus { position: static; width: auto; height: auto; margin: var(--spacing-2); padding: var(--spacing-2); background-color: var(--basic-green); color: var(--basic-white); } .header-content { display: flex; align-items: center; justify-content: space-between; padding: var(--spacing-4); } .logo { font-size: var(--font-xl); font-weight: var(--font-bold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 0; } .logo-link { text-decoration: none; color: inherit; transition: color 0.2s ease-in-out; } .logo-link:hover, .logo-link:focus { opacity: 0.75; } .hamburger { display: inline-flex; align-items: center; justify-content: center; width: var(--spacing-6); height: var(--spacing-6); border: none; background: transparent; cursor: pointer; border-radius: var(--spacing-1); transition: background-color 0.2s ease-in-out; } .hamburger:hover, .hamburger:focus { background-color: var(--basic-gray) 0.1; } @media (prefers-color-scheme: dark) { .hamburger { color: var(--dark-basic-white); } } .lines { position: relative; width: var(--spacing-6); height: var(--spacing-6); } .line { position: absolute; width: var(--spacing-6); height: 2px; background-color: currentColor; transition: transform 0.3s ease-in-out, top 0.3s ease-in-out, opacity 0.3s ease-in-out; } .line1 { top: 6px; } .line2 { top: 13px; } .line3 { bottom: 6px; } .open .line1 { top: 13px; transform: rotate(45deg); } .open .line2 { opacity: 0; } .open .line3 { bottom: 6px; transform: rotate(-45deg) translateY(-2px); } nav { display: none; } nav ul { list-style: none; margin: 0; padding: 0; } nav a { text-decoration: none; color: inherit; padding: var(--spacing-2) var(--spacing-1); display: block; transition: opacity 0.2s ease-in-out; } nav a:hover, nav a:focus { opacity: 0.75; } @media (min-width: 768px) { .hamburger { display: none; } nav { display: flex; align-items: center; gap: var(--spacing-8); } } .menu-container { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-in-out; border-top: none; background-color: var(--light-basic-white); } .menu-open { max-height: 400px; border-top: 1px solid var(--basic-gray); } @media (prefers-color-scheme: dark) { .menu-container { background-color: var(--dark-basic-black); } .menu-open { border-top: 1px solid var(--dark-basic-white); } } .menu-list { display: flex; flex-direction: column; gap: var(--spacing-4); padding: var(--spacing-4); list-style-type: none; } .menu { color: var(--light-basic-black); } @media (prefers-color-scheme: dark) { .menu { color: var(--dark-basic-white); } } @media (min-width: 768px) { .menu-container { display: none; } } `, ];
private toggleMenu() { this.isOpen = !this.isOpen; }
render() { return html` <header class="header"> <a href="#main" class="skip-link">Skip to main content</a> <div class="header-content"> <h1 class="logo"> <a href="/" class="logo-link">${this.logo}</a> </h1> <button class="hamburger ${this.isOpen ? "open" : ""}" @click="${this.toggleMenu}" aria-label="Toggle Menu" aria-expanded="${this.isOpen}" > <div class="lines"> <span class="line line1"></span> <span class="line line2"></span> <span class="line line3"></span> </div> </button> <nav> ${this.navItems.map( (item) => html` <a href="${item.href}">${item.label}</a> `, )} </nav> </div> <div class="menu-container ${this.isOpen ? "menu-open" : ""}" aria-hidden="${!this.isOpen}" > <ul class="menu-list"> ${this.navItems.map( (item) => html` <li> <a class="menu" href="${item.href}">${item.label}</a> </li> `, )} </ul> </div> </header> `; }}
Usage
Import the component and incorporate it into your pages or layouts. Override logo
and navItems
as needed.
Adjust the import paths according to each framework, and it’s convenient to manage the above code in separate files.
// import "@/components/MyHeader";export class AppLayout extends HTMLElement { connectedCallback() { this.innerHTML = ` <my-header logo="MyAwesomeSite" .navItems="${JSON.stringify([ { label: 'Home', href: '/' }, { label: 'Services', href: '/services' }, { label: 'Blog', href: '/blog' }, { label: 'Contact', href: '/contact' }, ])}" ></my-header> <main id="main"> <!-- Main content --> </main> `; }}