How Border and Background Styles Are Applied to Group Layers in CLI-Anything
CLI-Anything applies border and background styles to group layers by injecting an auxiliary background rectangle as the first child of the group, which carries the visual properties while the group itself functions as a pure container.
CLI-Anything is an open-source framework that converts natural language commands into structured design artifacts. When exporting to Sketch format, understanding how border and background styles are applied to group layers requires examining the builder.js harness, which translates JSON specifications into visual layer hierarchies.
The Background Rectangle Pattern
In the Sketch agent of HKUDS/CLI-Anything, group layers are strictly organizational containers that do not natively support visual styling properties. To achieve the appearance of borders or backgrounds on groups, the builder implements a proxy pattern using auxiliary rectangles.
Style Resolution in builder.js
Before constructing the layer tree, each specification undergoes style resolution. In sketch/agent-harness/src/builder.js, the resolveStyle function processes the style field of each node, resolving design token references into concrete color and dimension values. These resolved values are stored on the specification object as _resolvedStyle, making normalized backgroundColor, borderColor, borderWidth, and cornerRadius values available for downstream logic.
Group Construction Logic
When the builder encounters a node with type: 'group', it recursively processes children via buildLayerTree, then checks the _resolvedStyle object. If the style contains a backgroundColor or borderColor, the builder creates a rectangle using primitives.createRectangle with dimensions matching the group's frame. This rectangle is prepended to the children array before the group is instantiated via primitives.createGroup.
// sketch/agent-harness/src/builder.js – group case (excerpt)
case 'group': {
const children = buildLayerTree(spec.children || [], spec._childLayout || [], tokens);
// If the group has a background/border style, insert a bg rect first
const groupChildren = [];
if (resolved.backgroundColor || resolved.borderColor) {
groupChildren.push(
primitives.createRectangle({
name: (spec.name || 'Group') + '_bg',
x: 0,
y: 0,
width: frame.width,
height: frame.height,
backgroundColor: resolved.backgroundColor,
borderColor: resolved.borderColor,
borderWidth: resolved.borderWidth,
cornerRadius: resolved.cornerRadius || 0,
})
);
}
groupChildren.push(...children);
return primitives.createGroup(props, groupChildren);
}
Because this rectangle is positioned at (0, 0) with the full group dimensions and placed at the bottom of the stacking order, it visually serves as the group's background and border while actual child layers render on top.
Practical Implementation Examples
JSON Specification for Styled Groups
When defining a group in your specification, apply styles directly to the group node. The builder automatically extracts these properties and creates the background rectangle.
{
"type": "group",
"name": "Card",
"style": {
"backgroundColor": "#f0f4ff",
"borderColor": "#3366ff",
"borderWidth": 2,
"cornerRadius": 6
},
"children": [
{
"type": "rectangle",
"name": "Icon",
"width": 40,
"height": 40,
"style": { "backgroundColor": "#ffdd00" }
},
{
"type": "text",
"value": "Hello CLI‑Anything",
"fontSize": 14,
"style": { "color": "#222222" }
}
]
}
This specification produces a layer hierarchy where Card_bg carries the blue border and light blue background, while the Icon and text layers sit above it.
Programmatic Group Creation
You can replicate this behavior directly using the primitives API:
const { primitives } = require('./sketch/agent-harness/src/primitives');
const resolved = {
backgroundColor: '#f0f4ff',
borderColor: '#3366ff',
borderWidth: 2,
cornerRadius: 6,
};
// Create the background rectangle (as builder.js does automatically)
const bgRect = primitives.createRectangle({
name: 'Card_bg',
x: 0,
y: 0,
width: 200,
height: 120,
backgroundColor: resolved.backgroundColor,
borderColor: resolved.borderColor,
borderWidth: resolved.borderWidth,
cornerRadius: resolved.cornerRadius,
});
// Create content layers
const icon = primitives.createRectangle({
name: 'Icon', x: 10, y: 10, width: 40, height: 40
});
const label = primitives.createText({
name: 'Label', x: 60, y: 20, value: 'Hello'
});
// Assemble with background first to ensure correct z-order
const group = primitives.createGroup(
{ name: 'Card', x: 0, y: 0, width: 200, height: 120 },
[bgRect, icon, label]
);
Core Source Files
The border and background styling mechanism for group layers spans two primary files in the sketch/agent-harness directory:
src/builder.js: Contains theresolveStylefunction and the group case logic that conditionally inserts background rectangles based on resolved style properties.src/primitives.js: DefinescreateRectangleandcreateGroupfactory functions that generate the actual Sketch layer objects with proper property schemas.
Summary
- Group layers in CLI-Anything are style-agnostic containers that rely on auxiliary rectangles for visual properties.
resolveStylein builder.js normalizes token references and inline styles into concrete values before layer construction.- Background rectangles are injected as the first child of any group defining
backgroundColororborderColor, using the group's full frame dimensions. - The naming convention
GroupName_bgmakes these proxy layers identifiable in the resulting Sketch file hierarchy. - Stacking order is critical: The background rectangle must remain at the bottom of the group's child array for proper visual rendering.
Frequently Asked Questions
Why doesn't the group layer itself have background properties?
Sketch's group layer specification treats groups as organizational containers without inherent fill or stroke properties. CLI-Anything respects this constraint by generating a separate rectangle layer that visually represents the group's background, ensuring compatibility with the Sketch format while allowing designers to specify styles intuitively in the JSON spec.
What happens if a group has no background or border color defined?
If the resolved style lacks both backgroundColor and borderColor, the builder skips the rectangle creation entirely and passes only the content children to primitives.createGroup. The resulting group functions purely as a layout container with no auxiliary background layer.
Can corner radius be applied to group borders?
Yes. When the builder creates the background rectangle, it passes the cornerRadius value from the resolved style. The rectangle respects this property, creating rounded corners that visually appear as the group's border radius since the rectangle spans the entire group frame.
How does this pattern affect layer selection in Sketch?
Because the background is a distinct rectangle layer named with the _bg suffix, users can select it independently from the group container or its content children. This separation allows for individual manipulation of the background fill without affecting the group's structural organization or the positioning of nested elements.
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 →