Display

Tech Stack Row

A tech stack row with a caption: overlapping square tiles read left to right, and the list of names underneath spells the monograms out in words.

  • iconstack
  • stack
  • tech
  • overlap

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/iconstack-002?lang=en

React · Next.js · TypeScript · Tailwind

Project stack
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 "iconstack-002" (Tech Stack Row) 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/iconstack-002.json

Registry item: https://vibeui.ru/r/iconstack-002.json
Installs to: components/vibeui/iconstack-002.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 tech stack row with a caption: overlapping square tiles read left to right, and the list of names underneath spells the monograms out in words.

A tech stack row: overlapping monogram tiles plus a caption listing the names. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Iconstack002 } from "@/components/vibeui/iconstack-002"

<Iconstack002
  items={["React", "Next.js", "TypeScript"]}
  caption="Project stack"
  overlap={10}
/>

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-iconstack-002-* palette — do not swap it for your theme tokens
- the reversed flex instead of z-index: the left-to-right reading order comes from layout, not layers
- the square tiles: a circle clips product logos at the corners
- the tile ring in the surface colour — without it neighbouring tiles merge into a blob
- the row of names: monograms like "RE" and "NE" are unreadable on their own
- the component's own light surface: the stack caption is dark and vanishes on a dark background

## 6. You may change
- the caption under the stack through caption
- the overlap amount through overlap in pixels
- the stack contents through items
- the tile size through the --vibeui-iconstack-002-size variable

## 7. Rules
- The monogram takes the first two letters: "Next.js" and "Nest" both yield "NE".
- More than six tiles turn the stack into a stripe — trim the list beforehand.
- An overlap larger than half the tile hides the neighbours' monograms.
- 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/iconstack-002.json
https://vibeui.ru/r/iconstack-002.json

Серверный компонент, хуков нет. Перекрытие сделано обратным флексом (row-reverse и отрицательный margin-right), поэтому левая плитка ложится поверх правой без z-index на каждом элементе. Плитки — скруглённые квадраты, а не круги: продуктовые логотипы почти всегда квадратные, круг их обрезает. Оттенок каждой плитки выводится из имени FNV-хешем, поэтому набор технологий раскрашивается стабильно и без ручного словаря. Величина перекрытия вынесена в проп overlap и подставляется переменной в пикселях. Стопка декоративна и помечена 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 Iconstack002Props = Omit<
  ComponentPropsWithoutRef<"div">,
  "children"
> & {
  items?: string[]
  caption?: string
  overlap?: number
}

// Идея компонента: стопка технологий с подписью. В отличие от стопки людей
// здесь важен порядок чтения слева направо — первая технология главная,
// поэтому перекрытие сделано обратным флексом, и левая плитка лежит поверх
// правой без z-index на каждом элементе. Плитки скруглённые квадраты, а не
// круги: продуктовые логотипы почти всегда квадратные, круг их обрезает.
const STYLES = `
:where([data-vibeui-block="iconstack-002"]){
--vibeui-iconstack-002-size:2rem;
--vibeui-iconstack-002-overlap:10px;
--vibeui-iconstack-002-surface:oklch(1 0 0);
--vibeui-iconstack-002-border:oklch(0.9 0.006 265);
--vibeui-iconstack-002-fg:oklch(0.26 0.014 265);
--vibeui-iconstack-002-muted:oklch(0.55 0.014 265);
--vibeui-iconstack-002-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* Своя светлая подложка: подпись стека тёмная. */
[data-vibeui-block="iconstack-002"]{
display:inline-flex;flex-direction:column;gap:0.5rem;
box-sizing:border-box;padding:0.75rem 0.875rem;
background:var(--vibeui-iconstack-002-surface);
border:1px solid var(--vibeui-iconstack-002-border);border-radius:0.875rem;
font-family:var(--vibeui-iconstack-002-font);color:var(--vibeui-iconstack-002-fg);
}
/* Обратный флекс: левая плитка ложится поверх правой без z-index. */
[data-vibeui-block="iconstack-002"] [data-part="stack"]{
display:inline-flex;flex-direction:row-reverse;justify-content:flex-end;
}
[data-vibeui-block="iconstack-002"] [data-part="stack"] > *{
margin-right:calc(var(--vibeui-iconstack-002-overlap) * -1);
}
[data-vibeui-block="iconstack-002"] [data-part="stack"] > *:first-child{margin-right:0}
[data-vibeui-block="iconstack-002"] [data-part="chip"]{
display:inline-flex;align-items:center;justify-content:center;flex:none;
box-sizing:border-box;
width:var(--vibeui-iconstack-002-size);height:var(--vibeui-iconstack-002-size);
border-radius:0.5rem;
border:2px solid var(--vibeui-iconstack-002-surface);
background:oklch(0.92 0.06 var(--vibeui-iconstack-002-hue,265));
color:oklch(0.36 0.13 var(--vibeui-iconstack-002-hue,265));
font-size:0.6875rem;font-weight:700;line-height:1;
}
[data-vibeui-block="iconstack-002"] [data-part="caption"]{
font-size:0.75rem;color:var(--vibeui-iconstack-002-muted);
}
[data-vibeui-block="iconstack-002"] [data-part="names"]{
margin:0;font-size:0.8125rem;font-weight:600;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="iconstack-002"] *{animation:none!important;transition:none!important}}
`

const DEFAULT_ITEMS = ["React", "Next.js", "TypeScript", "Tailwind"]

function hue(name: string) {
  let hash = 2166136261
  for (const symbol of name) {
    hash ^= symbol.codePointAt(0)!
    hash = Math.imul(hash, 16777619)
  }
  return ((hash >>> 0) % 12) * 30
}

function monogram(name: string) {
  const letters = name.replace(/[^\p{L}\p{N}]/gu, "")
  return letters.slice(0, 2).toUpperCase()
}

/**
 * Стопка технологий: перекрытые плитки и подпись со списком названий.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Iconstack002({
  items = DEFAULT_ITEMS,
  caption = "Стек проекта",
  overlap = 10,
  className,
  style,
  ...props
}: Iconstack002Props) {
  const palette = {
    "--vibeui-iconstack-002-overlap": `${overlap}px`,
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-iconstack-002" precedence="medium">
        {STYLES}
      </style>
      <div
        {...props}
        data-vibeui-block="iconstack-002"
        className={className}
        style={palette}
      >
        <span data-part="stack" aria-hidden="true">
          {[...items].reverse().map((item) => (
            <span
              key={item}
              data-part="chip"
              style={
                { "--vibeui-iconstack-002-hue": hue(item) } as CSSProperties
              }
            >
              {monogram(item)}
            </span>
          ))}
        </span>
        <p data-part="names">{items.join(" · ")}</p>
        <span data-part="caption">{caption}</span>
      </div>
    </>
  )
}