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
createLabeledRectanglefunction inprimitives.jsorchestratescreateRectangle,createText, andcreateGroup. - Vertical centering uses a 1.4× font-size heuristic to calculate text positioning within the rectangle bounds.
- The
builder.jsmodule provides a declarative interface that automatically invokes the labeled rectangle factory when specs include bothtype: 'rectangle'and alabelproperty. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →