Categories: CSSScriptWeb Design

CSS corner-shape Arrow Tabs for Menus & Breadcrumbs

Arrow Nav Tabs is a CSS component that renders animated, arrow-shaped navigation tabs for site menus and breadcrumb trails.

The component combines the CSS corner-shape property with asymmetric border-radius values to create beveled arrow ends.

It relies on CSS Grid and the :has() selector to drive fluid width animations when you hover over or focus a tab.

Features:

  • Produces arrow-shaped tab visuals using CSS corner geometry and asymmetric border radii.
  • Animates per-tab width expansion on hover through grid column proportion changes.
  • Tracks hover state independently per tab item through sibling-count-aware CSS selectors.
  • Applies a spring-like bounce to the grid transition through a precision-tuned custom easing curve.
  • 9 configurable CSS custom properties for colors, spacing, bevel offset, and animation timing.
  • Highlights the active tab through URL fragment targeting.
  • Adapts to light and dark system color schemes automatically.
  • Fallbacks to a visible compatibility notice in browsers that do not support corner shaping.

How to use it:

1. Build a <nav> element containing anchor tags. Set each anchor’s href to point to its own id attribute. This activates the :target state on click and keeps a tab highlighted for as long as that URL fragment stays in the address bar.

<nav>
  <a href="#dashboard" id="dashboard">Dashboard</a>
  <a href="#projects"  id="projects">Projects</a>
  <a href="#analytics" id="analytics">Analytics</a>
  <a href="#settings"  id="settings">Settings</a>
  ...
</nav>

2. Apply the CSS. Paste the following styles into your stylesheet or a <style> block. The @layer declarations keep base resets and component styles separate, so they do not override your existing CSS.

/* Declare layer order: base resets load first, component styles override */@layer base, demo;

@layer demo {

  nav {
    /* Spring easing curve: defines the bounce behavior of the grid transition.
       The linear() function plots progress through dozens of control points,
       producing an overshoot-and-settle effect that cubic-bezier cannot replicate. */    --nav-item-trans-easing: linear(
      0, 0.009 0.7%, 0.04 1.5%, 0.154 3.1%, 0.801 9%, 1.026 11.5%,
      1.171 14%, 1.21 15.2%, 1.233 16.5%, 1.237 18.1%, 1.218 19.8%,
      1.004 28.5%, 0.963 31.2%, 0.945 33.9%, 0.948 37.3%, 0.999 46%,
      1.013 51.2%, 0.997 68.3%, 1
    );

    /* Total duration of the grid expansion animation */    --nav-item-trans-duration: 750ms;

    /* Default and hover background colors */    --nav-item-bg-color: rgb(29, 41, 61);
    --nav-item-bg-color-hover: rgb(69, 85, 108);

    /* Default and hover label colors */    --nav-item-text-color: white;
    --nav-item-text-color-hover: white;

    /* Border width — creates the visual gap between adjacent tabs */    --nav-item-spacing: 2px;

    /* Border color matches the page background to produce a clean separator */    --nav-item-border-color: var(--clr-bg);

    /* Depth of the diagonal bevel cut on each tab's right edge.
       Larger values produce a more pronounced arrow point. */    --nav-item-bevel-offset: 2em;

    /* Default 9-column grid layout: leftmost column is compressed */    --grid-cols: .5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;

    /* Per-tab hover rules: each one widens the column group under the active tab.
       The :has() selector detects which child has hover or keyboard focus. */    &:has(:nth-child(1):hover, :nth-child(1):focus-visible) {
      --grid-cols: 1.5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
    }
    &:has(:nth-child(2):hover, :nth-child(2):focus-visible) {
      --grid-cols: .5fr  1fr 1fr 2fr  1fr 1fr 1fr 1fr 1fr;
    }
    &:has(:nth-child(3):hover, :nth-child(3):focus-visible) {
      --grid-cols: .5fr  1fr 1fr 1fr  1fr 2fr 1fr 1fr 1fr;
    }
    &:has(:nth-child(4):hover, :nth-child(4):focus-visible) {
      --grid-cols: .5fr  1fr 1fr 1fr  1fr 1fr 1fr 1fr 2fr;
    }

    position: relative;
    display: grid;
    grid-template-columns: var(--grid-cols);
    width: min(100%, 800px);
    margin-inline: auto;

    /* Animate the column recalculation using the spring easing curve above */    transition: grid var(--nav-item-trans-duration) var(--nav-item-trans-easing);

    & > a {
      position: relative;

      /* The asymmetric border-radius creates zero rounding on the left side
         and a bevel cut on the right side, forming the arrow point.
         corner-shape: bevel renders the radius as a straight diagonal cut
         rather than the default curve. */      border-radius: 0 var(--nav-item-bevel-offset) var(--nav-item-bevel-offset) 0 /
                     0 50% 50% 0;
      corner-shape: bevel;

      /* This 150ms transition handles the background color swap on hover —
         it is intentionally faster than the grid animation for snappy feedback */      transition: all 150ms linear;
      border: var(--nav-item-spacing) solid var(--nav-item-border-color);
      display: grid;
      place-content: center;
      padding-block: 1em;
      background-color: var(--nav-item-bg-color);
      text-decoration: none;
      color: var(--nav-item-text-color);

      /* Fluid type scale: label size adjusts from small phones to wide monitors */      font-size: clamp(.65rem, 2.5vw + .04rem, 1.4rem);

      &:not(:first-child) {
        /* Left padding clears the bevel overhang from the previous tab,
           keeping label text horizontally clear of the overlap area */        padding-left: var(--nav-item-bevel-offset);
      }

      /* Active states: applied on hover, keyboard focus, and :target match.
         :target activates when the URL fragment equals this anchor's id. */      &:target,
      &:hover,
      &:focus-visible {
        color: var(--nav-item-text-color-hover);
        background-color: var(--nav-item-bg-color-hover);
        outline: none;
      }

      /* All tabs sit on row 1. Each spans 3 columns with a 2-column overlap.
         z-index decreases left to right so earlier tabs appear on top. */      grid-row: 1;
      &:nth-child(1) { grid-column: 1 / span 3; z-index: 4; }
      &:nth-child(2) { grid-column: 3 / span 3; z-index: 3; }
      &:nth-child(3) { grid-column: 5 / span 3; z-index: 2; }
      &:nth-child(4) { grid-column: 7 / span 3; z-index: 1; }
    }
  }
}

/* Base resets and color tokens for light/dark mode */@layer base {
  * {
    box-sizing: border-box;
  }

  :root {
    color-scheme: light dark;

    /* Page background tokens for each scheme */    --bg-dark:  rgb(12 10 9);
    --bg-light: rgb(248 245 238);
    --txt-light: rgb(10 10 10);
    --txt-dark:  rgb(245 245 245);

    /* Resolved tokens: the browser picks the correct value per active scheme */    --clr-bg:  light-dark(var(--bg-light), var(--bg-dark));
    --clr-txt: light-dark(var(--txt-light), var(--txt-dark));
    --clr-lines: light-dark(rgba(0 0 0 / .25), rgba(255 255 255 / .25));
  }

  body {
    background-color: var(--clr-bg);
    color: var(--clr-txt);
    min-height: 100svh;
    margin: 0;
    padding: 2rem;
    font-family: "Jura", sans-serif;
    font-size: 1rem;
    line-height: 1.5;
    display: grid;
    place-items: center;
    gap: 2rem;

    /* Fallback banner: visible in browsers that do not support corner-shape.
       Uses @supports to detect the feature and inject a fixed-position warning. */    @supports not (corner-shape: notch) {
      &::before {
        content: "Your browser does not support corner-shape. Arrow shapes will not render correctly.";
        position: fixed;
        top: 2rem;
        left: 50%;
        translate: -50% -50%;
        font-size: 0.8rem;
        color: white;
        background-color: red;
        padding: .25em 1em;
      }
    }
  }
}

3. CSS Custom Properties Reference:

  • --nav-item-bg-color (color): Sets the default background color for each tab.
  • --nav-item-bg-color-hover (color): Sets the background color for a tab in hover, focus, or active state.
  • --nav-item-text-color (color): Sets the default label color for tab items.
  • --nav-item-text-color-hover (color): Sets the label color when a tab is hovered, focused, or targeted.
  • --nav-item-spacing (length): Controls the border width that creates the visual gap between adjacent tabs.
  • --nav-item-border-color (color): Sets the border color. Matches the page background by default to produce a clean separator line.
  • --nav-item-bevel-offset (length): Controls the depth of the diagonal bevel cut on each tab’s right edge. Larger values create a sharper, more pronounced arrow point.
  • --nav-item-trans-duration (time): Sets the duration of the grid expansion animation triggered on hover.
  • --nav-item-trans-easing (easing): Defines the timing curve for the grid animation. Replace it with any valid CSS easing value to change the motion feel.

FAQs:

Q: How do I add a fifth tab?
A: Extend the grid to 11 columns and add a new :has(:nth-child(5):hover) rule that widens the column group under the fifth tab. Set its grid-column to 9 / span 3 and assign it z-index: 0. The HTML only needs a fifth anchor added to the <nav>.

Q: How do I use this in a React or Vue component?
A: Copy the CSS into your component’s stylesheet or a CSS module. React and Vue users can render the <nav> and anchor children directly inside their JSX or template.

Q: How do I set a tab as permanently active on page load?
A: Load the page with the relevant URL fragment already in the address bar (for example, yoursite.com/page#analytics). The :target pseudo-class matches automatically on load.

Q: Can I reverse the bevel to point left?
A: Swap the zero and offset values in the border-radius shorthand so the left horizontal radii receive var(--nav-item-bevel-offset) and the right ones receive zero. The corner-shape: bevel rule cuts the left corners instead. Adjust the padding-right on non-last items to compensate for the new overlap side.

The post CSS corner-shape Arrow Tabs for Menus & Breadcrumbs appeared first on CSS Script.

rssfeeds-admin

Share
Published by
rssfeeds-admin

Recent Posts

Hackers Use Fake Proxifier Installer on GitHub to Spread ClipBanker Crypto-Stealing Malware

A dangerous malware campaign has been silently targeting cryptocurrency users by hiding inside a fake…

7 minutes ago

Auto Group Proposes Replacing Gas Tax With Vehicle Weight Fee

WASHINGTON, D.C. (WOWO) A leading auto industry group is calling for a major shift in…

16 minutes ago

Auto Group Proposes Replacing Gas Tax With Vehicle Weight Fee

WASHINGTON, D.C. (WOWO) A leading auto industry group is calling for a major shift in…

16 minutes ago

Auto Group Proposes Replacing Gas Tax With Vehicle Weight Fee

WASHINGTON, D.C. (WOWO) A leading auto industry group is calling for a major shift in…

16 minutes ago

Indiana Warns of Eastern Tent Caterpillar Activity This Spring

INDIANAPOLIS, IND. (WOWO) Indiana landowners are being advised to monitor trees this spring as eastern…

16 minutes ago

Indiana Warns of Eastern Tent Caterpillar Activity This Spring

INDIANAPOLIS, IND. (WOWO) Indiana landowners are being advised to monitor trees this spring as eastern…

16 minutes ago

This website uses cookies.