Navigation

Badge Counters

Menu counters of three weights: a grey number for the ordinary count, a filled pill for the urgent one and a dot for "something new" without a number.

  • sidebar
  • badge
  • counter
  • navigation

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/sidebar-007?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "sidebar-007" (Badge Counters) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/sidebar-007.json

Registry item: https://vibeui.ru/r/sidebar-007.json
Installs to: components/vibeui/sidebar-007.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
Menu counters of three weights: a grey number for the ordinary count, a filled pill for the urgent one and a dot for "something new" without a number.

A menu with counters of three weights and large numbers capped at "99+". Zero dependencies, one file, no client JS.

## 3. How to use it
import { Sidebar007 } from "@/components/vibeui/sidebar-007"

<Sidebar007
  cap={99}
  activeLabel="Needs reply"
  items={[
    { label: "Inbox", href: "/inbox", count: 128, tone: "muted" },
    { label: "Needs reply", href: "/todo", count: 7, tone: "alert" },
    { label: "Updates", href: "/news", tone: "new" },
  ]}
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-sidebar-007-* palette — do not swap it for your theme tokens
- the three badge weights: one badge for everything turns the menu into a ripple of digits
- the cap on large numbers: the exact value is not read and the column width drifts
- the hidden label beside the number — "7" alone tells a screen reader nothing
- the dot instead of a number for "something new": having updates is not a count
- tabular numerals and the right-aligned badge: otherwise the numbers stop lining up

## 6. You may change
- the items through items: label, href, count and badge tone
- the cap of the displayed number through cap
- the current item through activeLabel
- the accent colour through accent

## 7. Rules
- Keep the alert tone to one or two items: three red pills stop being urgent.
- A zero count is rendered too: hiding empty counters is the application's job.
- The dot appears only on an item without a count and with the new tone — these are different signals.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/sidebar-007.json
https://vibeui.ru/r/sidebar-007.json

Компонент самодостаточен: один файл, без зависимостей, палитра в локальных переменных --vibeui-sidebar-007-*. Серверный: хуков нет. Вес бейджа задаётся атрибутом data-tone на строке списка: один одинаковый бейдж на всё превращает меню в рябь из цифр, и срочное в ней теряется. Большие числа обрезаются до «99+» по пропу cap — точное значение всё равно не читают, а ширина колонки от него уезжает. Смысл числа продублирован визуально скрытым словом: «7» без пояснения скринридеру ничего не говорит.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Sidebar007Item = {
  label: string
  href?: string
  count?: number
  /** Тон бейджа: обычный счётчик, требующий внимания или просто «новое». */
  tone?: "muted" | "alert" | "new"
}

export type Sidebar007Props = Omit<
  ComponentPropsWithoutRef<"nav">,
  "children"
> & {
  items?: Sidebar007Item[]
  activeLabel?: string
  /** Число, выше которого счётчик показывается как «99+». */
  cap?: number
  accent?: string
}

// Идея компонента: счётчики в меню разного веса. Обычный — серая цифра,
// требующий внимания — залитая плашка, «новое без числа» — точка. Один
// одинаковый бейдж на всё превращает меню в рябь из цифр, и срочное в нём
// теряется. Большие числа обрезаются до «99+»: точное значение всё равно
// никто не читает, а ширина колонки от него уезжает.
const STYLES = `
:where([data-vibeui-block="sidebar-007"]){
--vibeui-sidebar-007-bg:oklch(1 0 0);
--vibeui-sidebar-007-fg:oklch(0.25 0.016 265);
--vibeui-sidebar-007-muted:oklch(0.55 0.014 265);
--vibeui-sidebar-007-border:oklch(0.91 0.006 265);
--vibeui-sidebar-007-accent:oklch(0.55 0.19 262);
--vibeui-sidebar-007-alert:oklch(0.57 0.2 25);
--vibeui-sidebar-007-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="sidebar-007"]{
display:flex;flex-direction:column;gap:0.125rem;
width:100%;max-width:15rem;box-sizing:border-box;padding:0.625rem;
background:var(--vibeui-sidebar-007-bg);color:var(--vibeui-sidebar-007-fg);
border:1px solid var(--vibeui-sidebar-007-border);border-radius:0.875rem;
font-family:var(--vibeui-sidebar-007-font);
}
[data-vibeui-block="sidebar-007"] ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:0.125rem}
[data-vibeui-block="sidebar-007"] a{
display:flex;align-items:center;gap:0.5rem;
padding:0.4375rem 0.5rem;border-radius:0.5rem;
color:var(--vibeui-sidebar-007-muted);text-decoration:none;
font-size:0.875rem;line-height:1.3;
}
[data-vibeui-block="sidebar-007"] a:hover{background:oklch(0.55 0.02 265 / 7%);color:var(--vibeui-sidebar-007-fg)}
[data-vibeui-block="sidebar-007"] a:focus-visible{outline:2px solid var(--vibeui-sidebar-007-accent);outline-offset:-2px}
[data-vibeui-block="sidebar-007"] a[aria-current="page"]{
background:color-mix(in oklab,var(--vibeui-sidebar-007-accent) 12%,transparent);
color:var(--vibeui-sidebar-007-fg);font-weight:600;
}
[data-vibeui-block="sidebar-007"] [data-part="label"]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Счётчик прижат к правому краю распоркой: подписи разной длины, а цифры
   обязаны стоять по одной вертикали. */
[data-vibeui-block="sidebar-007"] [data-part="badge"]{
margin-left:auto;flex:none;
min-width:1.25rem;box-sizing:border-box;padding:0 0.3125rem;
border-radius:9999px;text-align:center;
font-size:0.6875rem;font-weight:700;line-height:1.125rem;
font-variant-numeric:tabular-nums;
background:oklch(0.55 0.02 265 / 10%);color:var(--vibeui-sidebar-007-muted);
}
[data-vibeui-block="sidebar-007"] [data-tone="alert"] [data-part="badge"]{
background:var(--vibeui-sidebar-007-alert);color:oklch(1 0 0);
}
/* Точка вместо числа: «есть новое» — это не количество. */
[data-vibeui-block="sidebar-007"] [data-part="dot"]{
margin-left:auto;flex:none;width:0.4375rem;height:0.4375rem;border-radius:9999px;
background:var(--vibeui-sidebar-007-accent);
}
[data-vibeui-block="sidebar-007"] [data-part="hint"]{
position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="sidebar-007"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ITEMS: Sidebar007Item[] = [
  { label: "Входящие", href: "#", count: 128, tone: "muted" },
  { label: "Требуют ответа", href: "#", count: 7, tone: "alert" },
  { label: "Черновики", href: "#", count: 2, tone: "muted" },
  { label: "Обновления", href: "#", tone: "new" },
  { label: "Архив", href: "#" },
]

/**
 * Меню со счётчиками трёх весов: серая цифра, срочная плашка и точка «новое».
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Sidebar007({
  items = DEFAULT_ITEMS,
  activeLabel = "Требуют ответа",
  cap = 99,
  accent,
  className,
  style,
  ...props
}: Sidebar007Props) {
  const palette = {
    ...(accent ? { "--vibeui-sidebar-007-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-sidebar-007" precedence="medium">
        {STYLES}
      </style>
      <nav
        {...props}
        data-vibeui-block="sidebar-007"
        aria-label="Почта"
        className={className}
        style={palette}
      >
        <ul>
          {items.map((item) => (
            <li key={item.label} data-tone={item.tone ?? "muted"}>
              <a
                href={item.href}
                aria-current={item.label === activeLabel ? "page" : undefined}
              >
                <span data-part="label">{item.label}</span>
                {item.count !== undefined ? (
                  <span data-part="badge">
                    {item.count > cap ? `${cap}+` : item.count}
                    <span data-part="hint"> непрочитанных</span>
                  </span>
                ) : null}
                {item.count === undefined && item.tone === "new" ? (
                  <>
                    <span data-part="dot" aria-hidden="true" />
                    <span data-part="hint">есть новое</span>
                  </>
                ) : null}
              </a>
            </li>
          ))}
        </ul>
      </nav>
    </>
  )
}