Generates or registers new page blocks with consistent component structure.

Install

mkdir -p .claude/skills/block && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18180" && unzip -o skill.zip -d .claude/skills/block && rm skill.zip

Installs to .claude/skills/block

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

app/blocks/{nombre}/ page.tsx ← Server Component: readFileSync + RequiredComponents {NombreBloque}Viewer.tsx ← "use client": SOLO si necesita estado propio (tabs, controles de efectos, colapso de sidebar…) Si el canvas es estático, meter todo en page.tsx. ```
259 chars · catalog descriptionno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Create a new page block with a canonical folder structure
  • Add a mobile variant to an existing block
  • Register an existing block without creating new files
  • Generate `dimensions.ts` files for width and height constants
  • Create a main component file (`{NombreBloque}.tsx`)
  • Register the block in `app/blocks/{nombre}/page.tsx`

How it works

The skill creates a structured folder architecture for a page block, including desktop and optional mobile components, dimension files, and registers it within the application's block system.

Inputs & outputs

You give it
A block name and optional flags for mobile variant or registration only
You get back
A structured folder architecture for the block, including component files and dimension constants

When to use block

  • Creating new UI blocks
  • Structuring page components
  • Registering new features in Voyager DS

About this skill

Skill: block

Crea o registra un bloque de página en Concorde (Voyager DS). Un bloque es una composición de componentes que representa una pantalla completa o sección mayor de la app.


Activación

/block NombreBloque                   # crea un bloque nuevo de cero
/block NombreBloque --mobile          # agrega variante mobile a un bloque existente
/block NombreBloque --register-only   # solo registra (ya tienes el .tsx)

Arquitectura canónica — SIEMPRE esta estructura

src/blocks/{nombre}/
  desktop/
    {NombreBloque}.tsx          ← componente principal ("use client" si tiene estado)
    dimensions.ts               ← OBLIGATORIO: módulo plano, exporta WIDTH/HEIGHT
    {Sub}.tsx                   ← sub-componentes opcionales (BidChat, ConsoleHeader…)
  mobile/                       ← solo si existe versión mobile
    {NombreBloque}Mobile.tsx    ← componente mobile
    dimensions.ts               ← OBLIGATORIO cuando hay mobile: MOBILE_WIDTH/HEIGHT
  shared/                       ← solo si hay datos compartidos desktop↔mobile
    {archivo}.ts                ← e.g. pills.ts, liveData.ts (sin "use client")

app/blocks/{nombre}/
  page.tsx                      ← Server Component: readFileSync + RequiredComponents
  {NombreBloque}Viewer.tsx      ← "use client": SOLO si necesita estado propio (tabs,
                                   controles de efectos, colapso de sidebar…)
                                   Si el canvas es estático, meter todo en page.tsx.

Por qué dimensions.ts es obligatorio

dimensions.ts es un módulo plano (sin "use client"). Eso permite que:

  • Server Components lo importen sin cruzar la barrera cliente.
  • Los cálculos de escala en los Viewers usen valores reales (no undefined → NaN).
  • El catálogo /blocks puede leer las dimensiones en build time.

Patrón en el componente:

// desktop/dimensions.ts  — sin "use client"
export const NOMBRE_WIDTH  = 798;
export const NOMBRE_HEIGHT = 1104;

// desktop/NombreBloque.tsx
import { NOMBRE_WIDTH, NOMBRE_HEIGHT } from "./dimensions";
export { NOMBRE_WIDTH, NOMBRE_HEIGHT } from "./dimensions";   // re-export para compat

Patrón en el Viewer:

// Importar desde dimensions.ts, NO desde el componente
import NombreBloque from "@/src/blocks/nombre/desktop/NombreBloque";
import { NOMBRE_WIDTH, NOMBRE_HEIGHT } from "@/src/blocks/nombre/desktop/dimensions";

PASO 1 — Crear el bloque (src/blocks/)

1.1 desktop/dimensions.ts

export const {NOMBRE}_WIDTH  = 0;   // reemplazar con valor real de Figma
export const {NOMBRE}_HEIGHT = 0;

1.2 desktop/{NombreBloque}.tsx

"use client";   // solo si el bloque tiene estado propio

import type { JSX } from "react";
import { {NOMBRE}_WIDTH, {NOMBRE}_HEIGHT } from "./dimensions";
export { {NOMBRE}_WIDTH, {NOMBRE}_HEIGHT } from "./dimensions";

export interface {NombreBloque}Props {
  className?: string;
}

export default function {NombreBloque}({ className = "" }: {NombreBloque}Props): JSX.Element {
  return (
    <div
      className={className}
      data-block="{nombre}"
      style={{ width: {NOMBRE}_WIDTH, minHeight: {NOMBRE}_HEIGHT, background: "#ffffff" }}
    >
      {/* montar componentes aquí */}
    </div>
  );
}

1.3 mobile/dimensions.ts (si aplica)

export const {NOMBRE}_MOBILE_WIDTH  = 420;
export const {NOMBRE}_MOBILE_HEIGHT = 0;   // reemplazar

1.4 shared/{archivo}.ts (si aplica)

Datos puros compartidos entre desktop y mobile (sin "use client"):

export type {Nombre}Variant = "variante1" | "variante2";
export const {NOMBRE}_DATA = { ... };

PASO 2 — Registrar en app/blocks/{nombre}/

2.1 page.tsx (Server Component)

import type { JSX } from "react";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import Header from "@/app/_components/Header";
import RequiredComponents, { type RequiredItem } from "@/app/blocks/_components/RequiredComponents";
import type { BlockFile } from "@/app/blocks/_components/BlockViewer";
import {NombreBloque}Viewer from "./{NombreBloque}Viewer";

function readSrc(rel: string): string {
  try { return readFileSync(join(process.cwd(), rel), "utf8"); }
  catch { return `// No se pudo leer ${rel} en build.`; }
}

const REQUIRED: RequiredItem[] = [
  // { name: "ComponenteX", path: "/handoff/componentex", role: "para qué sirve aquí" },
];

const BLOCK_FILES: BlockFile[] = [
  { path: "src/blocks/{nombre}/desktop/{NombreBloque}.tsx",  code: readSrc("src/blocks/{nombre}/desktop/{NombreBloque}.tsx") },
  { path: "src/blocks/{nombre}/desktop/dimensions.ts",       code: readSrc("src/blocks/{nombre}/desktop/dimensions.ts") },
  // mobile (si aplica):
  { path: "src/blocks/{nombre}/mobile/{NombreBloque}Mobile.tsx", code: readSrc("src/blocks/{nombre}/mobile/{NombreBloque}Mobile.tsx") },
  { path: "src/blocks/{nombre}/mobile/dimensions.ts",            code: readSrc("src/blocks/{nombre}/mobile/dimensions.ts") },
];

const USED_COMPONENTS = ["ComponenteA", "ComponenteB"];
const COMPONENT_FILES: BlockFile[] = USED_COMPONENTS.map(function toFile(name) {
  return { path: `src/components/${name}.tsx`, code: readSrc(`src/components/${name}.tsx`) };
});

const FILES: BlockFile[] = [...BLOCK_FILES, ...COMPONENT_FILES];

export default function {NombreBloque}BlockPage(): JSX.Element {
  return (
    <div style={{ minHeight: "100vh", background: "#ffffff", color: "#09090b", fontFamily: "var(--vmc-font-display,'Plus Jakarta Sans',-apple-system,sans-serif)" }}>
      <Header active="blocks" />
      <{NombreBloque}Viewer files={FILES} />
      <RequiredComponents items={REQUIRED} />
    </div>
  );
}

2.2 {NombreBloque}Viewer.tsx (Client — solo si hay estado)

"use client";

import type { JSX } from "react";
import {NombreBloque} from "@/src/blocks/{nombre}/desktop/{NombreBloque}";
import { {NOMBRE}_WIDTH, {NOMBRE}_HEIGHT } from "@/src/blocks/{nombre}/desktop/dimensions";
// mobile (si aplica):
import {NombreBloque}Mobile from "@/src/blocks/{nombre}/mobile/{NombreBloque}Mobile";
import { {NOMBRE}_MOBILE_WIDTH, {NOMBRE}_MOBILE_HEIGHT } from "@/src/blocks/{nombre}/mobile/dimensions";
import BlockViewer, { type BlockFile, VAULT_PREVIEW_BG } from "@/app/blocks/_components/BlockViewer";

export default function {NombreBloque}Viewer({ files }: { files: BlockFile[] }): JSX.Element {
  const canvas = <{NombreBloque} />;

  return (
    <BlockViewer
      id="{nombre}"
      description="Descripción de una línea del bloque."
      width={{NOMBRE}_WIDTH}
      height={{NOMBRE}_HEIGHT}
      canvas={canvas}
      canvasForViewport={{ mobile: { node: <{NombreBloque}Mobile />, width: {NOMBRE}_MOBILE_WIDTH, height: {NOMBRE}_MOBILE_HEIGHT } }}
      files={files}
      previewBg={VAULT_PREVIEW_BG}
    />
  );
}

Si el canvas es estático (sin controles), saltar el Viewer e integrar BlockViewer directamente en page.tsx (ver Sidebar como ejemplo).


PASO 3 — Registrar en el catálogo y navegación (bloque NUEVO)

3.1 app/blocks/_components/blocks-nav.ts

// Agregar al array BLOCKS_NAV:
{ id: "{nombre}", name: "{NombreBloque}" },

3.2 app/blocks/page.tsx

// Agregar al array BLOCKS:
{
  id: "{nombre}",
  name: "{NombreBloque}",
  width: {NOMBRE}_WIDTH,
  height: {NOMBRE}_HEIGHT,
  node: <{NombreBloque} />,
},

3.3 Correr el CLI registry

node scripts/build-registry.mjs
# Verificar: git diff --stat public/r

Checklist antes de hacer commit

  • src/blocks/{nombre}/desktop/dimensions.ts existe (módulo plano, sin "use client")
  • src/blocks/{nombre}/mobile/dimensions.ts existe (si hay mobile)
  • Viewer importa dimensiones desde dimensions.ts, NO desde el componente
  • app/blocks/{nombre}/page.tsxFILES incluye bloque + dimensions.ts + todos sus componentes
  • app/blocks/{nombre}/page.tsxREQUIRED tiene un RequiredItem por componente
  • blocks-nav.ts → entrada en BLOCKS_NAV
  • app/blocks/page.tsx → entrada en BLOCKS
  • Corrí node scripts/build-registry.mjs y git diff --stat public/r tiene sentido
  • npx tsc --noEmit pasa sin errores

Convenciones de nombres

BloqueCarpetaConstante desktopConstante mobile
Homepagehomepage/HOMEPAGE_WIDTH/HEIGHTHOMEPAGE_MOBILE_WIDTH/HEIGHT
Zonazona/ZONA_WIDTH/HEIGHTZONA_MOBILE_WIDTH/HEIGHT
MiBloquemi-bloque/MIBLOQUE_WIDTH/HEIGHTMIBLOQUE_MOBILE_WIDTH/HEIGHT

Regla: {NOMBRE_MAYUSCULAS}_{DIMENSION} — sin guiones, sin espacios.


Bloques con Sidebar (composición típica)

Los bloques que componen Sidebar + Header siguen el patrón de los Viewers de Homepage/Detalle/Zona:

const COMBINED_WIDTH  = SIDEBAR_WIDTH + BLOQUE_WIDTH;
const COMBINED_HEIGHT = Math.max(SIDEBAR_HEIGHT, HEADER_HEIGHT + BLOQUE_HEIGHT);
const EASE = "cubic-bezier(0.4, 0, 0.2, 1)";

const [collapsed, setCollapsed] = useState(false);
const fillW = COMBINED_WIDTH - (collapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH);
const scale = (fillW + 2) / BLOQUE_WIDTH;   // +2 = OVERSCAN px bajo el sidebar
const canvasH = Math.max(COMBINED_HEIGHT, (HEADER_HEIGHT + BLOQUE_HEIGHT) * scale);

Importar siempre las constantes del Sidebar desde su dimensions.ts:

import { SIDEBAR_WIDTH, SIDEBAR_HEIGHT, SIDEBAR_COLLAPSED_WIDTH } from "@/src/blocks/sidebar/desktop/dimensions";
import { HEADER_HEIGHT } from "@/src/blocks/header/desktop/dimensions";

When not to use it

  • When the user needs to create a component that is not a full screen or major section
  • When the user does not want to follow the canonical folder architecture
  • When the user wants to manually manage dimension constants

Limitations

  • Requires adherence to the canonical folder architecture
  • Dimensions.ts must be a plain module without 'use client'
  • The Viewer must import dimensions from dimensions.ts, not from the component

How it compares

This skill enforces a canonical folder architecture and dimension handling for UI blocks, ensuring consistency and proper client/server component separation, unlike manual component creation which may lack this structure.

Compared to similar skills

block side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
block (this skill)027dReviewIntermediate
figma-implement-design244moNo flagsAdvanced
ai-elements34moReviewBeginner
component-wrapper-architecture36moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

You might also like

figma-implement-design

openai

Translate Figma nodes into production-ready code with 1:1 visual fidelity using the Figma MCP workflow (design context, screenshots, assets, and project-convention translation). Trigger when the user provides Figma URLs or node IDs, or asks to implement designs or components that must match Figma specs. Requires a working Figma MCP server connection.

2460

ai-elements

vercel

Create new AI chat interface components for the ai-elements library following established composable patterns, shadcn/ui integration, and Vercel AI SDK conventions. Use when creating new components in packages/elements/src or when the user asks to add a new component to ai-elements.

323

component-wrapper-architecture

TheOrcDev

Best practices for wrapping shadcn/ui components. Apply when creating 8-bit styled variants of existing shadcn/ui components.

322

retro-css-architecture

TheOrcDev

Organize 8-bit CSS with custom properties, pixel fonts, and responsive pixel art. Apply when creating or modifying retro-styled components and their CSS.

13

prowler-ui

prowler-cloud

Prowler UI-specific patterns. For generic patterns, see: typescript, react-19, nextjs-15, tailwind-4. Trigger: When working inside ui/ on Prowler-specific conventions (shadcn vs HeroUI legacy, folder placement, actions/adapters, shared types/hooks/lib).

10

paper-to-code-components

millionco

Use when implementing Paper MCP, Paper-to-code, design-to-code, Viewfinder, or Paper-exported JSX in React/Next.js apps. Covers component candidates, globals.css tokens for arbitrary values, shadcn reuse, data-paper-* roots, and avoiding monolithic JSX.

00

Search skills

Search the agent skills registry