Kategorie
Navigation
Kein langweiliger Hover — kombinierte, fließende Nav-Effekte. Akzentfarbe über
--akzent in einer Zeile anpassbar.
Rail-Nav: Leiste wandert mit + Zoom & Shift
Pol 1 · Craft
Anlass: Sidebar / Doku-Nav
Die Indikator-Leiste folgt dem Punkt, über dem du bist; der Punkt zoomt leicht und rückt nach rechts (indirekter Call-to-Action). Deine Lieblings-Mechanik von animate-ui. Reagiert auch auf Tastatur-Fokus; beim Verlassen springt die Leiste zurück zum aktiven Punkt.
<nav class="fx-railnav" aria-label="Beispiel">
<span class="fx-railnav-bar" aria-hidden="true"></span>
<a href="#" class="active">Start</a>
<a href="#">Leistungen</a>
<a href="#">Über uns</a>
<a href="#">Kontakt</a>
</nav>
<style>
.fx-railnav{position:relative;display:inline-flex;flex-direction:column;gap:.15rem;padding-left:14px}
.fx-railnav-bar{position:absolute;left:0;top:0;width:2px;border-radius:2px;background:var(--akzent, #7c87ff);height:20px;
opacity:0;transition:transform .3s cubic-bezier(.4,0,.2,1),height .3s cubic-bezier(.4,0,.2,1),opacity .25s ease}
.fx-railnav a{position:relative;color:#9a9aa5;text-decoration:none;font-size:.95rem;padding:.35rem 0;
transform-origin:left center;transition:color .25s ease,transform .25s cubic-bezier(.4,0,.2,1)}
.fx-railnav a.active{color:#f4f4f5}
.fx-railnav a:hover,.fx-railnav a:focus-visible{color:#f4f4f5;transform:translateX(6px) scale(1.05)}
@media (prefers-reduced-motion:reduce){.fx-railnav a,.fx-railnav-bar{transition:none}.fx-railnav a:hover{transform:none}}
</style>
<script>
document.querySelectorAll('.fx-railnav').forEach(function(nav){
var bar=nav.querySelector('.fx-railnav-bar'), links=nav.querySelectorAll('a');
var active=nav.querySelector('a.active')||links[0];
function moveTo(el){bar.style.height=el.offsetHeight+'px';bar.style.transform='translateY('+el.offsetTop+'px)';bar.style.opacity='1';}
function reset(){moveTo(active);}
requestAnimationFrame(reset);
links.forEach(function(a){a.addEventListener('mouseenter',function(){moveTo(a);});a.addEventListener('focus',function(){moveTo(a);});});
nav.addEventListener('mouseleave',reset);
});
</script>
Schwebender Dock
Pol 1 · Craft
Anlass: dauerhafte Schnell-Navigation
Die Leiste, die ständig sichtbar bleibt (aceternity). Punkt hebt sich beim Hover/Fokus an und zeigt sein Label. Glyphen sind Platzhalter — im Projekt durch echte Icons ersetzen.
<div class="fx-dock">
<a href="#" data-label="Start">◆</a>
<a href="#" data-label="Leistungen">▣</a>
<a href="#" data-label="Galerie">◐</a>
<a href="#" data-label="Kontakt">✉</a>
</div>
<style>
.fx-dock{display:inline-flex;gap:.4rem;padding:.5rem .7rem;border-radius:99px;
background:rgba(255,255,255,.06);border:1px solid #2a2a33;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
.fx-dock a{position:relative;display:grid;place-items:center;width:40px;height:40px;border-radius:50%;
color:#c9c9d2;font-size:1.1rem;text-decoration:none;
transition:transform .25s cubic-bezier(.4,0,.2,1),background .25s ease,color .25s ease}
.fx-dock a:hover,.fx-dock a:focus-visible{transform:translateY(-6px) scale(1.15);background:var(--akzent, #7c87ff);color:#fff}
.fx-dock a::after{content:attr(data-label);position:absolute;bottom:118%;left:50%;
transform:translateX(-50%) translateY(4px);white-space:nowrap;font-size:.72rem;background:#101014;
border:1px solid #2a2a33;color:#e9e9ee;padding:.2rem .5rem;border-radius:6px;opacity:0;pointer-events:none;
transition:opacity .2s ease,transform .2s ease}
.fx-dock a:hover::after,.fx-dock a:focus-visible::after{opacity:1;transform:translateX(-50%) translateY(0)}
@media (prefers-reduced-motion:reduce){.fx-dock a,.fx-dock a::after{transition:none}.fx-dock a:hover{transform:none}}
</style>
Header-Nav: Unterstrich zieht ein
Pol 2 · Klarheit
Anlass: dezenter Header
Der ruhige Arbeitspferd-Effekt für Kundenseiten: Unterstrich wächst beim Hover/Fokus von links ein, aktiver Punkt bleibt unterstrichen. Zurückhaltend, nie verspielt.
<nav class="fx-undernav">
<a href="#" class="active">Start</a>
<a href="#">Leistungen</a>
<a href="#">Über uns</a>
<a href="#">Kontakt</a>
</nav>
<style>
.fx-undernav{display:inline-flex;gap:1.5rem}
.fx-undernav a{position:relative;color:#9a9aa5;text-decoration:none;font-size:.95rem;padding:.2rem 0;
transition:color .25s ease}
.fx-undernav a::after{content:"";position:absolute;left:0;bottom:-2px;height:2px;width:100%;background:var(--akzent, #7c87ff);
transform:scaleX(0);transform-origin:left;transition:transform .3s cubic-bezier(.4,0,.2,1)}
.fx-undernav a:hover,.fx-undernav a:focus-visible{color:#f4f4f5}
.fx-undernav a:hover::after,.fx-undernav a:focus-visible::after,.fx-undernav a.active::after{transform:scaleX(1)}
.fx-undernav a.active{color:#f4f4f5}
@media (prefers-reduced-motion:reduce){.fx-undernav a::after{transition:none}}
</style>
Header-Dropdown / Mega-Panel
Pol 1 · Craft
Anlass: Leistungs-Übersicht im Header
Glas-Panel mit zwei Spalten blendet bei Hover UND Tastatur-Fokus (:focus-within) ein; der Pfeil dreht sich. Der Hauptreiter behält einen klickbaren href (href-Durchlass), das Panel ist nur Ergänzung. Vorbild: Bernstein, NDS.
<nav class="fx-mega" aria-label="Beispiel">
<div class="fx-mega-item">
<a href="#">Leistungen <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg></a>
<div class="fx-mega-panel">
<a href="#"><b>Küchenmontage</b><span>Aufbau & Einrichtung</span></a>
<a href="#"><b>Reparatur</b><span>Schnelle Hilfe vor Ort</span></a>
<a href="#"><b>Renovierung</b><span>Komplett aus einer Hand</span></a>
<a href="#"><b>Beratung</b><span>Unverbindlich & kostenlos</span></a>
</div>
</div>
<div class="fx-mega-item"><a href="#">Über uns</a></div>
<div class="fx-mega-item"><a href="#">Kontakt</a></div>
</nav>
<style>
.fx-mega{display:inline-flex;gap:.4rem}
.fx-mega-item{position:relative}
.fx-mega-item>a{display:inline-flex;align-items:center;gap:.35rem;color:#a1a1aa;text-decoration:none;font-size:.95rem;
padding:.5rem .7rem;border-radius:8px;transition:color .2s ease,background .2s ease}
.fx-mega-item>a svg{width:12px;height:12px;transition:transform .25s cubic-bezier(.4,0,.2,1)}
.fx-mega-item:hover>a,.fx-mega-item:focus-within>a{color:#f4f4f5;background:rgba(255,255,255,.05)}
.fx-mega-item:hover>a svg,.fx-mega-item:focus-within>a svg{transform:rotate(180deg)}
.fx-mega-panel{position:absolute;top:calc(100% + 10px);left:0;min-width:320px;display:grid;grid-template-columns:1fr 1fr;gap:.3rem;
background:rgba(23,23,27,.92);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
border:1px solid #26262c;border-radius:14px;padding:10px;box-shadow:0 24px 48px -24px rgba(0,0,0,.7);
opacity:0;visibility:hidden;transform:translateY(6px);transition:opacity .22s ease,transform .22s ease,visibility .22s ease}
.fx-mega-item:hover .fx-mega-panel,.fx-mega-item:focus-within .fx-mega-panel{opacity:1;visibility:visible;transform:none}
.fx-mega-panel a{display:block;padding:10px 11px;border-radius:9px;text-decoration:none;border-left:2px solid transparent;
transition:background .18s ease,border-color .18s ease}
.fx-mega-panel a:hover,.fx-mega-panel a:focus-visible{background:rgba(255,255,255,.05);border-left-color:var(--akzent, #7c87ff)}
.fx-mega-panel b{display:block;font-size:.92rem;color:#f4f4f5;font-weight:600;margin-bottom:1px}
.fx-mega-panel span{font-size:.8rem;color:#a1a1aa}
@media (prefers-reduced-motion:reduce){.fx-mega-item>a,.fx-mega-item>a svg,.fx-mega-panel,.fx-mega-panel a{transition:none}}
</style>
Mobile-Nav-Drawer: Burger→X + einfahrendes Panel
Pol 2 · Klarheit
Anlass: Mobile-Hauptnavigation
Burger morpht zum X, ein Drawer fährt von rechts ein, ein abdunkelndes Overlay legt sich darüber. Schließt per Overlay-Klick und ESC, setzt aria-expanded. Hier in einer festen Demo-Box gezeigt; im Projekt ist der Drawer position:fixed über dem ganzen Viewport mit Body-Scroll-Lock. Vorbild: Hauszeit, Nördlicht.
<div class="fx-drawer-demo">
<div class="fx-drawer-topbar">
<b>Marke</b>
<button class="fx-burger" type="button" aria-expanded="false" aria-label="Menü öffnen" aria-controls="fx-drawer-1">
<span></span><span></span><span></span>
</button>
</div>
<div class="fx-drawer-overlay" data-open="false"></div>
<nav class="fx-drawer" id="fx-drawer-1" data-open="false" aria-label="Hauptmenü">
<a href="#">Start</a>
<a href="#">Leistungen</a>
<a href="#">Über uns</a>
<a href="#">Kontakt</a>
</nav>
</div>
<style>
/* In der Demo ist alles in einer Box (position:absolute). Im echten Header
stattdessen .fx-drawer / .fx-drawer-overlay auf position:fixed;inset:0 setzen
und beim Öffnen document.body.style.overflow='hidden' (Scroll-Lock). */
.fx-drawer-demo{position:relative;width:100%;max-width:360px;height:320px;overflow:hidden;border-radius:12px;border:1px solid #26262c;background:#16161a}
.fx-drawer-topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #26262c}
.fx-drawer-topbar b{color:#f4f4f5;font-size:.95rem;font-weight:600}
.fx-burger{display:inline-flex;flex-direction:column;gap:5px;width:44px;height:44px;align-items:center;justify-content:center;
background:none;border:0;cursor:pointer;border-radius:8px;transition:background .2s ease}
.fx-burger:hover,.fx-burger:focus-visible{background:rgba(255,255,255,.06)}
.fx-burger span{display:block;width:22px;height:2px;background:#e9e9ee;border-radius:2px;
transition:transform .3s cubic-bezier(.4,0,.2,1),opacity .2s ease}
.fx-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
.fx-burger[aria-expanded="true"] span:nth-child(2){opacity:0}
.fx-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
.fx-drawer-overlay{position:absolute;inset:0;background:rgba(0,0,0,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
opacity:0;visibility:hidden;transition:opacity .32s ease,visibility .32s ease;z-index:1}
.fx-drawer-overlay[data-open="true"]{opacity:1;visibility:visible}
.fx-drawer{position:absolute;inset:0 0 0 auto;width:min(78%,260px);background:#17171b;border-left:1px solid #26262c;
box-shadow:-16px 0 40px rgba(0,0,0,.4);display:flex;flex-direction:column;gap:.2rem;padding:64px 22px 24px;
transform:translateX(100%);visibility:hidden;transition:transform .32s cubic-bezier(.4,0,.2,1),visibility .32s ease;z-index:2}
.fx-drawer[data-open="true"]{transform:translateX(0);visibility:visible}
.fx-drawer a{color:#e9e9ee;text-decoration:none;font-size:1rem;font-weight:500;padding:13px 0;border-bottom:1px solid #26262c;transition:color .2s ease}
.fx-drawer a:last-of-type{border-bottom:0}
.fx-drawer a:hover,.fx-drawer a:focus-visible{color:var(--akzent, #7c87ff)}
@media (prefers-reduced-motion:reduce){.fx-burger span,.fx-drawer-overlay,.fx-drawer{transition:none}}
</style>
<script>
document.querySelectorAll('.fx-drawer-demo').forEach(function(box){
var burger=box.querySelector('.fx-burger'),
drawer=box.querySelector('.fx-drawer'),
overlay=box.querySelector('.fx-drawer-overlay');
function setOpen(open){
burger.setAttribute('aria-expanded',String(open));
burger.setAttribute('aria-label',open?'Menü schließen':'Menü öffnen');
drawer.dataset.open=String(open);
overlay.dataset.open=String(open);
/* Echtes Projekt: document.body.style.overflow = open ? 'hidden' : ''; */
}
burger.addEventListener('click',function(){
setOpen(burger.getAttribute('aria-expanded')!=='true');
});
overlay.addEventListener('click',function(){setOpen(false);});
drawer.querySelectorAll('a').forEach(function(a){a.addEventListener('click',function(){setOpen(false);});});
box.addEventListener('keydown',function(e){
if(e.key==='Escape'&&burger.getAttribute('aria-expanded')==='true'){setOpen(false);burger.focus();}
});
});
</script>
Mini-Sekundärmenü „Rechtliches"
Pol 2 · Klarheit
Anlass: FAQ / Datenschutz / Impressum
Kleiner Icon-Button öffnet ein kompaktes Dropdown für selten genutzte Links und entlastet so das Hauptmenü. Schließt per Außenklick und ESC, setzt aria-expanded. Vorbild: NDS.
<div class="fx-legal">
<button class="fx-legal-toggle" type="button" aria-expanded="false" aria-label="Rechtliches" aria-haspopup="true">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
</button>
<div class="fx-legal-dropdown" hidden>
<a href="#">FAQ</a>
<a href="#">Datenschutz</a>
<a href="#">Impressum</a>
</div>
</div>
<style>
.fx-legal{position:relative;display:inline-flex}
.fx-legal-toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;
background:rgba(255,255,255,.06);border:1px solid #26262c;border-radius:8px;color:#a1a1aa;cursor:pointer;
transition:background .2s ease,border-color .2s ease,color .2s ease}
.fx-legal-toggle svg{width:18px;height:18px}
.fx-legal-toggle:hover,.fx-legal-toggle:focus-visible{background:rgba(255,255,255,.1);border-color:#3a3a44;color:#f4f4f5}
.fx-legal-toggle[aria-expanded="true"]{border-color:var(--akzent, #7c87ff);color:var(--akzent, #7c87ff)}
.fx-legal-dropdown{position:absolute;top:calc(100% + 8px);right:0;min-width:200px;background:#17171b;border:1px solid #26262c;
border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.5);padding:6px;z-index:10}
.fx-legal-dropdown[hidden]{display:none}
.fx-legal-dropdown a{display:block;padding:9px 12px;color:#a1a1aa;font-size:.85rem;font-weight:500;border-radius:8px;
text-decoration:none;transition:background .18s ease,color .18s ease}
.fx-legal-dropdown a:hover,.fx-legal-dropdown a:focus-visible{background:rgba(255,255,255,.06);color:#f4f4f5}
@media (prefers-reduced-motion:reduce){.fx-legal-toggle,.fx-legal-dropdown a{transition:none}}
</style>
<script>
document.querySelectorAll('.fx-legal').forEach(function(wrap){
var btn=wrap.querySelector('.fx-legal-toggle'), dd=wrap.querySelector('.fx-legal-dropdown');
function close(){dd.setAttribute('hidden','');btn.setAttribute('aria-expanded','false');}
function open(){dd.removeAttribute('hidden');btn.setAttribute('aria-expanded','true');}
btn.addEventListener('click',function(e){e.stopPropagation();dd.hasAttribute('hidden')?open():close();});
document.addEventListener('click',function(e){if(!wrap.contains(e.target))close();});
document.addEventListener('keydown',function(e){if(e.key==='Escape')close();});
});
</script>
Aktiv-Pille-Nav: Hover-Pille + wachsender Unterstrich
Pol 2 · Klarheit
Anlass: Haupt-Header mit aktiver Seite
Links bekommen beim Hover/Fokus eine dezente Pille und einen kurz einwachsenden Unterstrich; die aktive Seite ist als gefüllte Akzent-Pille dauerhaft markiert. Klarer Orientierungs-Anker. Vorbild: Nördlicht.
<nav class="fx-pillnav" aria-label="Beispiel">
<a href="#" class="active" aria-current="page">Start</a>
<a href="#">Leistungen</a>
<a href="#">Über uns</a>
<a href="#">Kontakt</a>
</nav>
<style>
.fx-pillnav{display:inline-flex;gap:.3rem}
.fx-pillnav a{position:relative;color:#a1a1aa;text-decoration:none;font-size:.95rem;font-weight:500;padding:.5rem .9rem;
border-radius:99px;transition:color .25s ease,background .25s ease}
.fx-pillnav a::after{content:"";position:absolute;left:50%;bottom:6px;width:0;height:2px;border-radius:2px;
background:var(--akzent, #7c87ff);transform:translateX(-50%);transition:width .3s cubic-bezier(.4,0,.2,1)}
.fx-pillnav a:hover,.fx-pillnav a:focus-visible{color:#f4f4f5;background:rgba(255,255,255,.05)}
.fx-pillnav a:hover::after,.fx-pillnav a:focus-visible::after{width:42%}
.fx-pillnav a.active{color:#f4f4f5;background:var(--akzent, #7c87ff)}
.fx-pillnav a.active::after{width:0}
@media (prefers-reduced-motion:reduce){.fx-pillnav a,.fx-pillnav a::after{transition:none}}
</style>
Zoom-Fokus-Nav: Hervorheben per Skalierung statt Farbe
Pol 1 · Craft
Anlass: Header ohne Akzentfarbe
Der aktive und der gehoverte Punkt wird nicht eingefärbt , sondern per
weichem Zoom nach vorn geholt — neutrale Hervorhebung ohne Akzentfarbe. Feder-Kurve für einen dezenten
Pop, der aktive Punkt zusätzlich mit kleinem neutralem Marker. Funktioniert horizontal wie vertikal.
<nav class="fx-zoomnav" aria-label="Navigation">
<a href="#" aria-current="page">Start</a>
<a href="#">Leistungen</a>
<a href="#">Über uns</a>
<a href="#">Kontakt</a>
</nav>
<style>
/* Hervorhebung per Skalierung — keine Akzentfarbe */
.fx-zoomnav{display:inline-flex;gap:2.4rem;padding:10px 6px}
.fx-zoomnav a{position:relative;color:#8a8a93;text-decoration:none;font-size:1rem;font-weight:600;
transform-origin:center bottom;transition:transform .28s cubic-bezier(.34,1.56,.64,1),color .22s ease}
.fx-zoomnav a:hover,.fx-zoomnav a:focus-visible,.fx-zoomnav a[aria-current="page"]{color:#f4f4f5;transform:scale(1.22);outline:none}
.fx-zoomnav a[aria-current="page"]::after{content:"";position:absolute;left:50%;bottom:-9px;width:5px;height:5px;border-radius:50%;background:currentColor;transform:translateX(-50%)}
@media (prefers-reduced-motion:reduce){.fx-zoomnav a{transition:color .22s ease}
.fx-zoomnav a:hover,.fx-zoomnav a:focus-visible,.fx-zoomnav a[aria-current="page"]{transform:none}}
</style>
Breadcrumb: Pfad mit Chevron-Trennern
Pol 2 · Klarheit
Anlass: Unterseiten-Orientierung, Shop-Kategorie
Ruhige Pfad-Anzeige mit dezenten Chevron-Trennern; jeder Vorfahr ist ein anklickbarer Link, die aktuelle Seite bleibt inaktiv und markiert (aria-current="page"). Umschließendes nav mit aria-label, Trenner sind aria-hidden. Bricht bei Platzmangel sauber um.
<nav class="fx-crumb" aria-label="Brotkrumen">
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 4l9 6.5"/><path d="M5 9.5V20h14V9.5"/></svg>Start</a>
<span class="sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 6 6 6-6 6"/></svg></span>
<a href="#">Leistungen</a>
<span class="sep" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 6 6 6-6 6"/></svg></span>
<span aria-current="page">Küchenmontage</span>
</nav>
<style>
.fx-crumb{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem;font-size:.9rem}
.fx-crumb a{display:inline-flex;align-items:center;gap:.4rem;color:#a1a1aa;text-decoration:none;
padding:.3rem .55rem;border-radius:8px;transition:color .2s ease,background .2s ease}
.fx-crumb a svg{width:14px;height:14px;flex:0 0 auto}
.fx-crumb a:hover,.fx-crumb a:focus-visible{color:#f4f4f5;background:rgba(255,255,255,.05);outline:none}
.fx-crumb a:focus-visible{outline:2px solid var(--akzent, #7c87ff);outline-offset:2px}
.fx-crumb .sep{color:#4a4a54;flex:0 0 auto;display:inline-flex}
.fx-crumb .sep svg{width:13px;height:13px}
.fx-crumb [aria-current="page"]{color:#f4f4f5;font-weight:600;padding:.3rem .55rem}
@media (prefers-reduced-motion:reduce){.fx-crumb a{transition:none}}
</style>
Tab-Leiste mit gleitendem Indikator
Pol 1 · Craft
Anlass: Inhalts-Umschalter, Dashboard-Reiter
Eine gemeinsame Kachel gleitet zum aktiven Reiter, statt dass jeder Tab seinen eigenen Hintergrund hat — das ergibt eine durchgehende Bewegung. Winziges JS misst nur Breite und Position des aktiven Buttons; volle Tastatur-Bedienung mit Pfeiltasten. Rolle tablist/tab gesetzt. Bei reduzierter Bewegung springt die Kachel ohne Gleiten.
Übersicht
Leistungen
Galerie
Kontakt
<div class="fx-tabs" role="tablist" aria-label="Ansicht">
<span class="fx-tabs-ink" aria-hidden="true"></span>
<button type="button" role="tab" aria-selected="true">Übersicht</button>
<button type="button" role="tab" aria-selected="false">Leistungen</button>
<button type="button" role="tab" aria-selected="false">Galerie</button>
<button type="button" role="tab" aria-selected="false">Kontakt</button>
</div>
<style>
.fx-tabs{position:relative;display:inline-flex;gap:.2rem;padding:5px;border-radius:12px;
background:rgba(255,255,255,.04);border:1px solid #26262c;max-width:100%;overflow:auto}
.fx-tabs-ink{position:absolute;top:5px;left:0;height:calc(100% - 10px);border-radius:8px;
background:rgba(255,255,255,.07);border:1px solid #33333d;width:0;
transform:translateX(0);opacity:0;pointer-events:none;
transition:transform .32s cubic-bezier(.4,0,.2,1),width .32s cubic-bezier(.4,0,.2,1),opacity .2s ease}
.fx-tabs button{position:relative;z-index:1;border:0;background:none;cursor:pointer;white-space:nowrap;
color:#a1a1aa;font-size:.9rem;font-weight:600;padding:.55rem 1rem;border-radius:8px;
transition:color .22s ease}
.fx-tabs button:hover,.fx-tabs button:focus-visible{color:#f4f4f5;outline:none}
.fx-tabs button:focus-visible{outline:2px solid var(--akzent, #7c87ff);outline-offset:2px}
.fx-tabs button[aria-selected="true"]{color:#f4f4f5}
@media (prefers-reduced-motion:reduce){.fx-tabs-ink,.fx-tabs button{transition:none}}
</style>
<script>
document.querySelectorAll('.fx-tabs').forEach(function(bar){
var ink=bar.querySelector('.fx-tabs-ink');
var tabs=Array.prototype.slice.call(bar.querySelectorAll('button'));
if(!ink||!tabs.length) return;
function moveInk(el){
ink.style.width=el.offsetWidth+'px';
ink.style.transform='translateX('+(el.offsetLeft-bar.clientLeft)+'px)';
ink.style.opacity='1';
}
function select(el){
tabs.forEach(function(t){t.setAttribute('aria-selected',String(t===el));});
moveInk(el);
}
tabs.forEach(function(t,i){
t.addEventListener('click',function(){select(t);});
t.addEventListener('keydown',function(e){
var dir=e.key==='ArrowRight'?1:e.key==='ArrowLeft'?-1:0;
if(!dir) return;
e.preventDefault();
var next=tabs[(i+dir+tabs.length)%tabs.length];
next.focus();select(next);
});
});
var sel=bar.querySelector('[aria-selected="true"]')||tabs[0];
requestAnimationFrame(function(){moveInk(sel);});
});
</script>
Segment-Control: gleitende Akzent-Kachel
Pol 2 · Klarheit
Anlass: Filter, Preis-Umschalter (Monat/Jahr)
Kompakter Umschalter aus echten Radio-Buttons — komplett ohne JavaScript. Die Akzent-Kachel gleitet per :checked-Selektor zur gewählten Option, die Radios sind sichtbar versteckt aber voll per Tastatur bedienbar (Pfeiltasten). Braucht genau drei Optionen; für mehr die nth-of-type-Regeln erweitern.
Abrechnung
Monatlich
Jährlich
Einmalig
<fieldset class="fx-seg">
<legend class="sr-only">Abrechnung</legend>
<input type="radio" name="fxseg" id="fxseg-1" checked>
<input type="radio" name="fxseg" id="fxseg-2">
<input type="radio" name="fxseg" id="fxseg-3">
<span class="thumb" aria-hidden="true"></span>
<label for="fxseg-1">Monatlich</label>
<label for="fxseg-2">Jährlich</label>
<label for="fxseg-3">Einmalig</label>
</fieldset>
<style>
/* fieldset: border:0 (kommt hier über die Klasse), margin:0, padding:4px */
.fx-seg{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;
padding:4px;border-radius:99px;background:#101014;border:1px solid #26262c;max-width:100%;margin:0}
.fx-seg .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.fx-seg input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.fx-seg label{position:relative;z-index:1;text-align:center;white-space:nowrap;cursor:pointer;
padding:.5rem 1.1rem;border-radius:99px;color:#a1a1aa;font-size:.88rem;font-weight:600;
transition:color .25s ease}
.fx-seg .thumb{position:absolute;top:4px;bottom:4px;left:4px;z-index:0;border-radius:99px;
background:var(--akzent, #7c87ff);width:calc((100% - 8px) / 3);
transform:translateX(0);transition:transform .3s cubic-bezier(.4,0,.2,1)}
.fx-seg input:nth-of-type(2):checked~.thumb{transform:translateX(100%)}
.fx-seg input:nth-of-type(3):checked~.thumb{transform:translateX(200%)}
.fx-seg input:nth-of-type(1):checked~label:nth-of-type(1),
.fx-seg input:nth-of-type(2):checked~label:nth-of-type(2),
.fx-seg input:nth-of-type(3):checked~label:nth-of-type(3){color:#fff}
.fx-seg input:nth-of-type(1):focus-visible~label:nth-of-type(1),
.fx-seg input:nth-of-type(2):focus-visible~label:nth-of-type(2),
.fx-seg input:nth-of-type(3):focus-visible~label:nth-of-type(3){outline:2px solid #fff;outline-offset:-2px;border-radius:99px}
@media (prefers-reduced-motion:reduce){.fx-seg label,.fx-seg .thumb{transition:none}}
</style>
Vertikale Schritt-Nav: Stepper mit Fortschritt
Pol 2 · Klarheit
Anlass: Checkout, Onboarding, Formular-Schritte
Nummerierte Schritte auf einer durchgehenden Linie; erledigte Schritte bekommen einen gefüllten Akzent-Punkt mit Haken und färben die Linie darüber, der aktuelle Schritt ist per Akzent-Ring hervorgehoben. Reines CSS — Zustand kommt nur über die Klassen done / current. Semantische Liste, aria-current="step" am aktiven Punkt.
Angaben Kontaktdaten erfasst
Leistung wählen Küchenmontage gebucht
3
Termin Wunschdatum auswählen
4
Bestätigen Übersicht & Abschluss
<ol class="fx-steps">
<li class="done">
<span class="dot" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 5 5L20 7"/></svg></span>
<b>Angaben</b><span>Kontaktdaten erfasst</span>
</li>
<li class="current">
<span class="dot" aria-current="step">3</span>
<b>Termin</b><span>Wunschdatum auswählen</span>
</li>
<li>
<span class="dot" aria-hidden="true">4</span>
<b>Bestätigen</b><span>Übersicht & Abschluss</span>
</li>
</ol>
<style>
.fx-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.fx-steps li{position:relative;padding:0 0 22px 34px}
.fx-steps li:last-child{padding-bottom:0}
.fx-steps li::before{content:"";position:absolute;left:11px;top:22px;bottom:-2px;width:2px;background:#26262c}
.fx-steps li:last-child::before{display:none}
.fx-steps .dot{position:absolute;left:0;top:0;width:24px;height:24px;border-radius:50%;
display:grid;place-items:center;background:#16161a;border:2px solid #33333d;color:#a1a1aa;
font-size:.72rem;font-weight:700;transition:border-color .25s ease,background .25s ease,color .25s ease}
.fx-steps .dot svg{width:13px;height:13px}
.fx-steps b{display:block;color:#e9e9ee;font-size:.95rem;font-weight:600;line-height:1.4}
.fx-steps span{display:block;color:#a1a1aa;font-size:.82rem;margin-top:1px}
.fx-steps li.done .dot{background:var(--akzent, #7c87ff);border-color:var(--akzent, #7c87ff);color:#fff}
.fx-steps li.done::before{background:var(--akzent, #7c87ff)}
.fx-steps li.current .dot{border-color:var(--akzent, #7c87ff);color:#f4f4f5;
box-shadow:0 0 0 4px color-mix(in srgb,var(--akzent, #7c87ff) 22%,transparent)}
.fx-steps li.current b{color:#f4f4f5}
@media (prefers-reduced-motion:reduce){.fx-steps .dot{transition:none}}
</style>
Scrollspy-Ankernav: markiert den Abschnitt im Blick
Pol 1 · Craft
Anlass: Doku, lange Landingpage, Ratgeber
Die seitliche Ankernav hebt automatisch den Punkt hervor, dessen Abschnitt gerade sichtbar ist — hier über einen IntersectionObserver statt Scroll-Rechnerei. Klick scrollt sanft zum Anker. In der Bühne als eigenständige Scroll-Box gezeigt; im Projekt beobachtet der Observer die echten Seiten-Abschnitte und die Nav wird position:sticky. Bei reduzierter Bewegung ohne Sanft-Scroll.
<div class="fx-spy">
<nav class="fx-spy-nav" aria-label="Abschnitte">
<a href="#fxspy-a" class="active">Leistungen</a>
<a href="#fxspy-b">Ablauf</a>
<a href="#fxspy-c">Preise</a>
<a href="#fxspy-d">Kontakt</a>
</nav>
<div class="fx-spy-scroll">
<section id="fxspy-a"><h4>Leistungen</h4><p>…</p></section>
<section id="fxspy-b"><h4>Ablauf</h4><p>…</p></section>
<section id="fxspy-c"><h4>Preise</h4><p>…</p></section>
<section id="fxspy-d"><h4>Kontakt</h4><p>…</p></section>
</div>
</div>
<style>
.fx-spy{display:flex;gap:16px;align-items:flex-start;border:1px solid #26262c;border-radius:14px;
background:#17171b;overflow:hidden;max-width:100%}
.fx-spy-nav{flex:0 0 128px;position:sticky;top:0;align-self:flex-start;padding:14px 10px;
display:flex;flex-direction:column;gap:.1rem;border-right:1px solid #26262c;background:#16161a}
.fx-spy-nav a{position:relative;color:#a1a1aa;text-decoration:none;font-size:.85rem;font-weight:500;
padding:.4rem .5rem .4rem .7rem;border-radius:7px;transition:color .2s ease,background .2s ease}
.fx-spy-nav a::before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%) scaleY(0);
width:2px;height:60%;border-radius:2px;background:var(--akzent, #7c87ff);transition:transform .25s ease}
.fx-spy-nav a:hover,.fx-spy-nav a:focus-visible{color:#f4f4f5;background:rgba(255,255,255,.04);outline:none}
.fx-spy-nav a:focus-visible{outline:2px solid var(--akzent, #7c87ff);outline-offset:1px}
.fx-spy-nav a.active{color:#f4f4f5}
.fx-spy-nav a.active::before{transform:translateY(-50%) scaleY(1)}
.fx-spy-scroll{flex:1 1 auto;min-width:0;height:220px;overflow:auto;padding:14px 16px;scroll-behavior:smooth}
.fx-spy-scroll h4{margin:0 0 6px;color:#f4f4f5;font-size:.95rem;scroll-margin-top:14px}
.fx-spy-scroll section{padding-bottom:18px}
.fx-spy-scroll p{margin:0;color:#a1a1aa;font-size:.85rem;line-height:1.6}
@media (prefers-reduced-motion:reduce){
.fx-spy-nav a,.fx-spy-nav a::before{transition:none}
.fx-spy-scroll{scroll-behavior:auto}
}
</style>
<script>
document.querySelectorAll('.fx-spy').forEach(function(spy){
var scroll=spy.querySelector('.fx-spy-scroll');
var links=Array.prototype.slice.call(spy.querySelectorAll('.fx-spy-nav a'));
var sections=Array.prototype.slice.call(spy.querySelectorAll('.fx-spy-scroll section'));
if(!scroll||!links.length||!sections.length||!('IntersectionObserver' in window)) return;
function setActive(id){
links.forEach(function(a){
a.classList.toggle('active',a.getAttribute('href')==='#'+id);
});
}
var io=new IntersectionObserver(function(entries){
entries.forEach(function(en){ if(en.isIntersecting) setActive(en.target.id); });
},{root:scroll,rootMargin:'-10% 0px -70% 0px',threshold:0});
sections.forEach(function(s){io.observe(s);});
links.forEach(function(a){
a.addEventListener('click',function(e){
var t=spy.querySelector(a.getAttribute('href'));
if(t){e.preventDefault();t.scrollIntoView({behavior:'smooth',block:'start'});setActive(t.id);}
});
});
});
</script>