Badge

Truncating Badge

A badge for text of unpredictable length: end truncation is left to CSS, middle truncation to code, and the full text stays available.

  • badge
  • truncate
  • overflow
  • monospace

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/badge-023?lang=en

feature/registry-badge-overflow
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 "badge-023" (Truncating Badge) 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/badge-023.json

Registry item: https://vibeui.ru/r/badge-023.json
Installs to: components/vibeui/badge-023.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
A badge for text of unpredictable length: end truncation is left to CSS, middle truncation to code, and the full text stays available.

A badge that truncates long text in two modes: from the end via CSS and in the middle via code. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Badge023 } from "@/components/vibeui/badge-023"

<Badge023 width={16} truncate="middle">
  feature/registry-badge-overflow
</Badge023>

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-badge-023-* palette — do not swap it for your theme tokens
- the full text in title and in the hidden caption: truncation must not lose the content
- the ellipsis on the inner node rather than the flex container — otherwise the string is cut with no mark
- middle truncation for names with a shared prefix: "feature/very-long-…" is indistinguishable from a dozen neighbours
- the monospace font: it makes a width in ch predictable
- the badge's own light surface: without it the dark text disappears on a dark background

## 6. You may change
- the width in characters through width
- the truncation mode through truncate
- the text through children
- the badge radius and height in the block rules

## 7. Rules
- children is typed as a string: middle truncation works on text, not on a node tree.
- The ch unit measures the width of a zero — in a proportional font the real string is wider, which is why middle mode keeps four characters of slack.
- title is not shown on touch screens: if the text is critical, repeat it next to the badge.
- 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/badge-023.json
https://vibeui.ru/r/badge-023.json

Один файл, ноль зависимостей, палитра в локальных переменных --vibeui-badge-023-*. Серверный: хуков нет. Ширина задаётся в ch и кладётся в --vibeui-badge-023-width инлайновым стилем. Режим end работает штатным text-overflow:ellipsis, причём многоточие живёт на внутреннем узле: у flex-контейнера text-overflow не срабатывает и строка просто отрезалась бы без знака. Режим middle CSS не умеет вовсе, поэтому строку режет функция shorten — голова и хвост считаются от лимита, между ними многоточие; у имени ветки и у пути к файлу значимы оба края. Полный текст уходит в title и в скрытую подпись, а обрезанный вариант помечается aria-hidden.

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 Badge023Props = Omit<
  ComponentPropsWithoutRef<"span">,
  "children"
> & {
  children?: string
  width?: number
  truncate?: "end" | "middle"
}

// Идея компонента: плашка для текста, длину которого никто не гарантирует —
// имени ветки, названия файла, адреса. Два режима обрезки: с конца отрезает
// CSS, а по середине — код, потому что у ветки и у файла значимы оба края,
// и «feature/very-long-…» неотличимо от десятка соседей. Полный текст в
// title и в доступном имени: обрезка не должна терять содержание.
const STYLES = `
:where([data-vibeui-block="badge-023"]){
--vibeui-badge-023-width:16ch;
--vibeui-badge-023-bg:oklch(0.96 0.005 265);
--vibeui-badge-023-fg:oklch(0.32 0.014 265);
--vibeui-badge-023-border:oklch(0.89 0.006 265);
--vibeui-badge-023-font:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
}
[data-vibeui-block="badge-023"]{
position:relative;
display:inline-flex;align-items:center;box-sizing:border-box;
max-width:var(--vibeui-badge-023-width);
height:1.625rem;padding:0 0.625rem;
border:1px solid var(--vibeui-badge-023-border);border-radius:0.4375rem;
background:var(--vibeui-badge-023-bg);color:var(--vibeui-badge-023-fg);
font-family:var(--vibeui-badge-023-font);font-size:0.75rem;font-weight:500;line-height:1;
white-space:nowrap;vertical-align:middle;
}
/* Многоточие живёт на самом тексте: у flex-контейнера text-overflow не
   срабатывает, он просто отрезал бы строку без знака. */
[data-vibeui-block="badge-023"] [data-part="text"]{
min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
[data-vibeui-block="badge-023"][data-truncate="end"]{overflow:hidden}
/* Обрезка по середине CSS не умеет, поэтому строку режет код. Запас в
   четыре знака — на то, что ch считается по нулю, а буквы шире. */
[data-vibeui-block="badge-023"][data-truncate="middle"]{
overflow:hidden;max-width:calc(var(--vibeui-badge-023-width) + 4ch);
}
[data-vibeui-block="badge-023"] [data-part="sr"]{
position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
clip-path:inset(50%);white-space:nowrap;border:0;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="badge-023"] *{animation:none!important;transition:none!important}}
`

function shorten(text: string, width: number) {
  if (text.length <= width) {
    return text
  }

  const head = Math.ceil((width - 1) / 2)
  const tail = Math.max(1, width - 1 - head)

  return `${text.slice(0, head)}…${text.slice(text.length - tail)}`
}

/**
 * Плашка с усечением длинного текста: с конца или по середине.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Badge023({
  children = "feature/registry-badge-overflow",
  width = 16,
  truncate = "middle",
  className,
  style,
  ...props
}: Badge023Props) {
  const limit = Math.max(4, Math.round(width))
  const text = truncate === "middle" ? shorten(children, limit) : children
  const clipped = text !== children

  const palette = {
    "--vibeui-badge-023-width": `${limit}ch`,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-badge-023" precedence="medium">
        {STYLES}
      </style>
      <span
        {...props}
        data-vibeui-block="badge-023"
        data-truncate={truncate}
        className={className}
        style={palette}
        title={children}
      >
        <span data-part="text" aria-hidden={clipped || undefined}>
          {text}
        </span>
        {clipped ? <span data-part="sr">{children}</span> : null}
      </span>
    </>
  )
}