How HKUDS/CLI-Anything Creates Labeled Rectangles (Buttons) with Background and Text

HKUDS/CLI-Anything creates labeled rectangles by composing a styled Rectangle primitive and a centered Text primitive into a Sketch Group, using a 1.4× font-size heuristic to calculate vertical alignment.

The CLI-Anything toolkit generates Sketch-compatible UI elements from declarative JSON specifications. When you need button-like components, the library does not rely on a native "button" type; instead, it programmatically assembles labeled rectangles through a specific six-step composition pattern implemented in the primitive layer factories.

The Six-Step Composition Pipeline

1. Style Preparation via buildStyle

In sketch/agent-harness/src/primitives.js, the buildStyle function aggregates visual properties including backgroundColor, borderColor, borderWidth, cornerRadius, and optional shadow configurations into a Sketch-compatible style object.

2. Rectangle Generation with createRectangle

The createRectangle factory receives geometry and style properties, returning a sketch-constructor Rectangle layer instance. This handles the background fill and border rendering.

3. Text Layer Construction via createText

The createText function maps font family and weight to PostScript names (fontName), sets the string content, size, color, and alignment, then patches Style.textStyle to ensure font and color attributes are respected by Sketch's rendering engine.

4. Vertical Centering Calculation

To center text vertically within the rectangle, the library approximates line height as fontSize × 1.4. The Y-offset is computed as (rectHeight - textHeight) / 2, positioning the text layer precisely in the middle of the background rectangle.

5. Grouping with createGroup

Both the rectangle and text layers are wrapped in a Group via createGroup. The group inherits the caller's x, y, width, and height, allowing the entire button-like element to be positioned and manipulated as a single unit.

6. Builder Integration

In sketch/agent-harness/src/builder.js, when a layer specification contains type: 'rectangle' alongside a label object, the builder resolves the rectangle properties and invokes createLabeledRectangle, bridging high-level JSON definitions to low-level Sketch primitives.

Implementation Details from the Source Code

According to the source code in primitives.js, the createLabeledRectangle function is not a native Sketch API but a composite utility that orchestrates the factories mentioned above. The text positioning logic specifically calculates:

const textHeight = labelProps.fontSize * 1.4;
const yOffset = (rectangleProps.height - textHeight) / 2;

This approximation provides sufficient accuracy for single-line button labels without requiring complex text metrics from Sketch's native APIs.

Practical Usage Examples

Direct Primitive Factory

When you need programmatic control, import createLabeledRectangle directly from the primitives module:

const { createLabeledRectangle } = require('./sketch/agent-harness/src/primitives');

const button = createLabeledRectangle(
  {
    x: 10,
    y: 20,
    width: 200,
    height: 48,
    backgroundColor: '#0066FF',
    borderColor: '#0044AA',
    borderWidth: 2,
    cornerRadius: 6,
  },
  {
    value: 'Submit',
    fontSize: 18,
    fontWeight: 'bold',
    color: '#FFFFFF',
  }
);

artboard.addLayer(button);

Declarative Builder API

For JSON-driven workflows, use buildLayerTree in builder.js:

const { buildLayerTree } = require('./sketch/agent-harness/src/builder');

const spec = {
  type: 'rectangle',
  name: 'SaveButton',
  x: 30,
  y: 100,
  width: 120,
  height: 40,
  style: {
    backgroundColor: '#28A745',
    borderColor: '#1E7E34',
    borderWidth: 1,
    cornerRadius: 4,
  },
  label: {
    value: 'Save',
    style: { fontSize: 16, fontWeight: 'bold', color: '#FFF' },
  },
};

const layers = buildLayerTree([spec], [{ x: 30, y: 100, width: 120, height: 40 }], {});
artboard.addLayer(layers[0]);

Adding Drop Shadows

Extend the rectangle properties with a shadow configuration:

const btn = createLabeledRectangle(
  {
    x: 0,
    y: 0,
    width: 150,
    height: 50,
    backgroundColor: '#FF5722',
    cornerRadius: 8,
    shadow: {
      color: '#00000033',
      blurRadius: 8,
      offsetX: 0,
      offsetY: 4,
    },
  },
  { value: 'Delete', fontSize: 14, color: '#FFF' }
);

Summary

  • HKUDS/CLI-Anything implements labeled rectangles as composite Groups, not native Sketch button types.
  • The createLabeledRectangle function in primitives.js orchestrates createRectangle, createText, and createGroup.
  • Vertical centering uses a 1.4× font-size heuristic to calculate text positioning within the rectangle bounds.
  • The builder.js module provides a declarative interface that automatically invokes the labeled rectangle factory when specs include both type: 'rectangle' and a label property.
  • All visual styling—backgrounds, borders, shadows, and typography—passes through the standard property bags consumed by the primitive factories.

Frequently Asked Questions

Is createLabeledRectangle a native Sketch API or a custom composite?

It is a custom composite implemented in sketch/agent-harness/src/primitives.js. The function wraps a Rectangle layer and a Text layer into a Group, applying calculated offsets to center the text vertically.

How does CLI-Anything calculate vertical centering for button labels?

The library approximates text height as fontSize × 1.4, then computes the Y-offset using (rectHeight - textHeight) / 2. This positions the text layer at the visual center of the background rectangle without requiring native text metric queries.

Can I use custom fonts with the labeled rectangle factory?

Yes. The createText primitive maps fontFamily and fontWeight to PostScript font names (fontName) and patches Style.textStyle. Pass your desired font properties in the label configuration object.

Where does the builder logic detect that a rectangle should have a label?

In sketch/agent-harness/src/builder.js, the layer processor checks for the presence of a label object in the specification. When type: 'rectangle' and label both exist, it routes the props to createLabeledRectangle instead of the standard rectangle factory.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →