Warm Editorial — navigation
The signature element of the Warm Editorial language. Circle badge and two-line brand on the left, links in the middle, pill CTA on the right. On mobile the links drop into a panel that keeps the same corners and blur.
Paste this into Claude, Cursor or Copilot — or point the agent at
https://ui-deploy.com/r/warm-editorial/nav-floating/prompt.md
# Floating Pill Nav — Warm Editorial
Paste this whole file into Claude, Cursor or Copilot. It is self-contained:
the agent gets the tokens, the rules, and the reference implementation.
---
## What to build
A primary site navigation that **floats over the content** rather than sticking to
the top edge of the viewport. Fully rounded (999px), translucent with a backdrop
blur, sitting 16px down from the top with its own breathing room on the sides.
Left to right:
1. A circle badge holding a small stroke icon
2. A two-line brand block — name on top, an uppercase, wide-tracked eyebrow below
3. Navigation links, centred
4. A pill CTA button on the right
Below 860px the links move into a panel that drops beneath the bar and keeps the
same corners, border and blur. The CTA hides; a round menu button appears.
---
## Tokens this component needs
Warm Editorial is a warm palette: no pure black, no pure white, no cold grey.
Light is the default; dark is opted into with `data-theme="dark"` on `<html>`.
```css
:root{
--bg2:#F6F3ED; --bg3:#E4DED2; --border:#DAD3C6;
--text:#22201D; --text2:#5C574F;
--accent:#A38A6A; --accent-text:#7D6644;
--accent-s:rgba(163,138,106,.12); --accent-b:rgba(163,138,106,.30);
--radius-lg:24px; --radius-pill:999px;
--shadow-md:0 18px 48px -24px rgba(34,32,29,.22);
--font-display:'Inter Tight',system-ui,sans-serif;
--font-body:'Inter',system-ui,sans-serif;
--ease:cubic-bezier(.22,.61,.36,1);
}
[data-theme="dark"]{
--bg2:#1E1A17; --bg3:#262119; --border:#2E2822;
--text:#EDE7DD; --text2:#A79E92;
--accent:#B39B7C; --accent-text:#C0A888;
--accent-s:rgba(179,155,124,.13); --accent-b:rgba(179,155,124,.28);
--shadow-md:0 18px 48px -24px rgba(0,0,0,.7);
}
```
Never write a raw hex in the component. Every colour comes from a token.
---
## Rules — these are not preferences
1. **It floats.** `position:sticky; top:16px` with horizontal padding on the wrapper.
Pinning it flush to the top edge kills the entire effect.
2. **A gap under the bar is a hover trap.** If a dropdown or panel sits below the bar
with empty space between them, the pointer crossing that gap fires `mouseleave`
and the menu escapes. Either open on click (as here) or bridge the gap with an
invisible `::before`.
3. **The closed mobile panel must be `visibility:hidden`**, not just `opacity:0`.
Opacity alone leaves every link in the tab order, so keyboard users tab into
an invisible menu.
4. **`:focus-visible` sets `outline` only.** Do not set `border-radius` in a focus
rule — it deforms the shape of the focused pill.
5. **Icons are inline SVG**, `stroke="currentColor"`, `fill="none"`. No emoji, no
icon font, no icon library.
6. **Leaving mobile width with the panel open must close it**, or the panel is
stranded visible on desktop.
7. **No horizontal scroll at 320px.**
8. Transitions are `.2s`. Nothing in this language animates longer than `.7s`.
---
## Reference implementation
See `code.html` in this component folder — HTML, scoped CSS and 12 lines of
dependency-free JS. Copy it verbatim, then change the brand name, links and CTA.
---
## Adapting it
- **React / Vue / Svelte** — the markup maps one to one. Replace the `aria-expanded`
toggle with local state; keep the class names so the CSS is untouched.
- **Tailwind** — keep this CSS as-is in a stylesheet. The values here are a design
language, not utilities; rewriting them as classes loses the tokens.
- **Fewer links** — the centred `margin:0 auto` on the list holds the layout. With
two or fewer links, consider `justify-content:flex-end` instead.
- **Logo image instead of the badge** — keep the 36px circle footprint so the 60px
bar height still reads correctly.
Paste this whole file into Claude, Cursor or Copilot. It is self-contained: the agent gets the tokens, the rules, and the reference implementation.
A primary site navigation that floats over the content rather than sticking to the top edge of the viewport. Fully rounded (999px), translucent with a backdrop blur, sitting 16px down from the top with its own breathing room on the sides.
Left to right:
Below 860px the links move into a panel that drops beneath the bar and keeps the same corners, border and blur. The CTA hides; a round menu button appears.
Warm Editorial is a warm palette: no pure black, no pure white, no cold grey. Light is the default; dark is opted into with data-theme="dark" on <html>.
:root{
--bg2:#F6F3ED; --bg3:#E4DED2; --border:#DAD3C6;
--text:#22201D; --text2:#5C574F;
--accent:#A38A6A; --accent-text:#7D6644;
--accent-s:rgba(163,138,106,.12); --accent-b:rgba(163,138,106,.30);
--radius-lg:24px; --radius-pill:999px;
--shadow-md:0 18px 48px -24px rgba(34,32,29,.22);
--font-display:'Inter Tight',system-ui,sans-serif;
--font-body:'Inter',system-ui,sans-serif;
--ease:cubic-bezier(.22,.61,.36,1);
}
[data-theme="dark"]{
--bg2:#1E1A17; --bg3:#262119; --border:#2E2822;
--text:#EDE7DD; --text2:#A79E92;
--accent:#B39B7C; --accent-text:#C0A888;
--accent-s:rgba(179,155,124,.13); --accent-b:rgba(179,155,124,.28);
--shadow-md:0 18px 48px -24px rgba(0,0,0,.7);
}
Never write a raw hex in the component. Every colour comes from a token.
position:sticky; top:16px with horizontal padding on the wrapper. Pinning it flush to the top edge kills the entire effect.mouseleave and the menu escapes. Either open on click (as here) or bridge the gap with an invisible ::before.visibility:hidden, not just opacity:0. Opacity alone leaves every link in the tab order, so keyboard users tab into an invisible menu.:focus-visible sets outline only. Do not set border-radius in a focus rule — it deforms the shape of the focused pill.stroke="currentColor", fill="none". No emoji, no icon font, no icon library..2s. Nothing in this language animates longer than .7s.See code.html in this component folder — HTML, scoped CSS and 12 lines of dependency-free JS. Copy it verbatim, then change the brand name, links and CTA.
aria-expandedtoggle with local state; keep the class names so the CSS is untouched.
language, not utilities; rewriting them as classes loses the tokens.
margin:0 auto on the list holds the layout. With two or fewer links, consider justify-content:flex-end instead.
bar height still reads correctly.
<!-- Warm Editorial — Floating pill nav
Requires tokens.css. Drop this markup where the nav belongs, add the
<style> and <script> once per page. -->
<header class="we-nav-wrap">
<nav class="we-nav" aria-label="Main">
<a class="we-nav-brand" href="/">
<span class="we-nav-badge" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round">
<path d="M4 18 L12 6 L20 18"/><path d="M8.5 14h7"/>
</svg>
</span>
<span class="we-nav-name">
<strong>Atelier</strong>
<em>Recovery Studio</em>
</span>
</a>
<ul class="we-nav-links" id="we-nav-links">
<li><a href="#approach">Approach</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#studio">Studio</a></li>
<li><a href="#journal">Journal</a></li>
</ul>
<a class="we-nav-cta" href="#book">Book a visit</a>
<button class="we-nav-toggle" type="button"
aria-expanded="false" aria-controls="we-nav-links" aria-label="Menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round">
<path d="M4 8h16"/><path d="M4 16h16"/>
</svg>
</button>
</nav>
</header>
<style>
.we-nav-wrap{position:sticky;top:16px;z-index:50;padding:0 16px}
.we-nav{
display:flex;align-items:center;gap:16px;
max-width:76rem;margin:0 auto;height:60px;padding:0 8px 0 12px;
background:color-mix(in srgb, var(--bg2) 82%, transparent);
-webkit-backdrop-filter:blur(16px) saturate(1.4);
backdrop-filter:blur(16px) saturate(1.4);
border:1px solid var(--border);
border-radius:var(--radius-pill);
box-shadow:var(--shadow-md);
}
/* Brand ------------------------------------------------------------------ */
.we-nav-brand{display:flex;align-items:center;gap:10px;text-decoration:none;flex:0 0 auto}
.we-nav-badge{
display:grid;place-items:center;width:36px;height:36px;flex:0 0 36px;
border-radius:50%;background:var(--accent-s);
border:1px solid var(--accent-b);color:var(--accent-text);
}
.we-nav-badge svg{width:18px;height:18px}
.we-nav-name{display:flex;flex-direction:column;line-height:1.1}
.we-nav-name strong{
font-family:var(--font-display);font-size:15px;font-weight:600;
letter-spacing:-.015em;color:var(--text)
}
.we-nav-name em{
font-family:var(--font-display);font-size:10.5px;font-weight:600;font-style:normal;
letter-spacing:.16em;text-transform:uppercase;color:var(--accent-text);margin-top:2px
}
/* Links ------------------------------------------------------------------ */
.we-nav-links{
display:flex;align-items:center;gap:4px;
margin:0 auto;padding:0;list-style:none
}
.we-nav-links a{
display:block;padding:8px 14px;border-radius:var(--radius-pill);
font-family:var(--font-body);font-size:14px;font-weight:500;
color:var(--text2);text-decoration:none;
transition:background .2s var(--ease), color .2s var(--ease)
}
.we-nav-links a:hover{background:var(--bg3);color:var(--text)}
/* CTA -------------------------------------------------------------------- */
.we-nav-cta{
display:inline-flex;align-items:center;height:44px;padding:0 22px;flex:0 0 auto;
border-radius:var(--radius-pill);background:var(--text);color:var(--bg);
font-family:var(--font-body);font-size:14px;font-weight:500;text-decoration:none;
transition:opacity .2s var(--ease)
}
.we-nav-cta:hover{opacity:.85}
/* Toggle — desktop hidden ------------------------------------------------ */
.we-nav-toggle{
display:none;place-items:center;width:44px;height:44px;flex:0 0 44px;
padding:0;background:var(--bg3);border:1px solid var(--border);
border-radius:50%;color:var(--text);cursor:pointer
}
.we-nav-toggle svg{width:20px;height:20px}
/* Focus — outline only. Never set border-radius here: it deforms the shape. */
.we-nav a:focus-visible,
.we-nav button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
/* Mobile ----------------------------------------------------------------- */
@media (max-width:860px){
.we-nav{gap:10px;padding:0 8px 0 12px}
.we-nav-cta{display:none}
.we-nav-toggle{display:grid}
.we-nav-links{
position:absolute;top:calc(100% + 8px);left:0;right:0;
flex-direction:column;align-items:stretch;gap:2px;margin:0;padding:8px;
background:color-mix(in srgb, var(--bg2) 92%, transparent);
-webkit-backdrop-filter:blur(16px) saturate(1.4);
backdrop-filter:blur(16px) saturate(1.4);
border:1px solid var(--border);
border-radius:var(--radius-lg);
box-shadow:var(--shadow-md);
/* visibility, not just opacity — otherwise the closed menu stays in Tab order */
opacity:0;visibility:hidden;transform:translateY(-6px);
transition:opacity .2s var(--ease), transform .2s var(--ease), visibility .2s
}
.we-nav-links.is-open{opacity:1;visibility:visible;transform:none}
.we-nav-links a{padding:12px 14px;font-size:15px}
.we-nav{position:relative}
}
@media (prefers-reduced-motion:reduce){
.we-nav *{transition:none !important}
}
</style>
<script>
(function () {
var nav = document.currentScript.closest('body') || document;
var btn = nav.querySelector('.we-nav-toggle');
var list = nav.querySelector('#we-nav-links');
if (!btn || !list) return;
function setOpen(open) {
list.classList.toggle('is-open', open);
btn.setAttribute('aria-expanded', String(open));
}
btn.addEventListener('click', function () {
setOpen(btn.getAttribute('aria-expanded') !== 'true');
});
list.addEventListener('click', function (e) {
if (e.target.closest('a')) setOpen(false);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') setOpen(false);
});
// Leaving mobile width with the panel open would strand it visible.
matchMedia('(min-width:861px)').addEventListener('change', function (e) {
if (e.matches) setOpen(false);
});
})();
</script>
Needs tokens.css on the page.