Skip to content

Repository files navigation

rw-elements-tools

The official build toolkit for creating RapidWeaver Elements

Build powerful, reusable web components for RapidWeaver without the complexity. This toolkit handles the heavy lifting so you can focus on what matters: creating great elements.


What is rw-elements-tools?

rw-elements-tools is a development toolkit that simplifies the process of creating custom elements for RapidWeaver, the popular Mac website builder. It provides:

  • A powerful CLI for building and watching your element files
  • Ready-to-use controls for common UI patterns (colors, spacing, typography, and more)
  • Shared utilities for generating Tailwind CSS classes
  • Tree shaking that ships only the shared code each component actually uses

Who is this for?

  • Theme developers who want to create custom RapidWeaver elements
  • Agencies building bespoke elements for client projects
  • Developers looking to extend RapidWeaver's capabilities
  • Anyone who wants to contribute elements to the RapidWeaver ecosystem

Why use rw-elements-tools?

Without rw-elements-toolsWith rw-elements-tools
Manually write complex JSON config filesUse intuitive JavaScript configuration
Copy/paste utility code between elementsImport from a shared library
Bloated output with unused codeAutomatic tree shaking
Manual rebuilds on every changeWatch mode for instant updates

Installation

npm install --save-dev rw-elements-tools

Quick Start

Step 1: Set Up Your Project

Create a new directory for your element pack project and initialize it:

mkdir my-element-pack
cd my-element-pack
npm init -y
npm install --save-dev rw-elements-tools

Step 2: Create the Required Directory Structure

Your project needs a packs folder containing your element pack(s). Each pack follows this structure:

my-element-pack/
├── package.json
├── packs/ # Default packs directory
│ └── MyPack.elementsdevpack/ # Your pack (must end in .elementsdevpack)
│ └── components/
│ └── com.yourcompany.elementname/ # Component folder (must start with com.)
│ ├── properties.config.json # Source config (you edit this)
│ ├── properties.json # Generated output (don't edit)
│ ├── hooks.source.js # Source hooks (you edit this)
│ └── hooks.js # Generated output (don't edit)
└── node_modules/

Key naming conventions:

  • Pack folders must end with .elementsdevpack
  • Component folders must start with com. (e.g., com.mycompany.button)
  • Source files: properties.config.json and hooks.source.js
  • Generated files: properties.json and hooks.js

Step 3: Create Your First Element

Create the folder structure for your first element:

mkdir -p packs/MyPack.elementsdevpack/components/com.mycompany.button

Create a minimal properties.config.json:

{
"groups": [
{
"title": "Content",
"icon": "text.alignleft",
"properties": [
{
"title": "Button Text",
"id": "buttonText",
"text": {
"default": "Click Me"
}
}
]
}
]
}

Create a hooks.source.js that uses the shared hook utilities:

functiontransformHook(rw){// Props are accessed via rw.props - property IDs from properties.config.jsonconst{ buttonText }=rw.props;// Build CSS classes using the shared classnames utilityconstclasses=classnames().add(globalSpacing(rw)).add(globalBgColor(rw)).toString();return{
classes,
buttonText
};}exports.transformHook=transformHook;

Note: The buttonText prop corresponds to the "id": "buttonText" defined in properties.config.json. All property IDs become available on rw.props. Functions like classnames(), globalSpacing(), and globalBgColor() come from the shared hooks library—no imports needed.

Step 4: Add Build Scripts

Add these scripts to your package.json:

{
"scripts": {
"build": "rw-build all",
"build:properties": "rw-build properties",
"build:hooks": "rw-build hooks",
"dev": "rw-build all --watch"
}
}

Step 5: Build Your Elements

# One-time build
npm run build
# Or watch for changes during development
npm run dev

That's it! The build tool will generate properties.json and hooks.js files in each component folder.


Build Commands Reference

# Build all properties and hooks
npx rw-build all
# Build properties only
npx rw-build properties
# Build hooks only
npx rw-build hooks
# Watch for changes
npx rw-build all --watch # Watch both properties and hooks
npx rw-build properties --watch # Watch properties only
npx rw-build hooks --watch # Watch hooks only# AI property coverage audit
npx rw-build audit # Shared controls + default core pack (if found)
npx rw-build audit --no-core-packs # Shared controls only
npx rw-build audit --packs ./packs # Audit one additional packs directory
npx rw-build audit --packs ./a --packs ./b # Audit multiple additional roots
npx rw-build audit --core-packs ../MyCorePack/packs # Custom core pack directory
npx rw-build audit --out-dir ./reports/ai-audit # Custom report output directory

The audit writes to audit/ai-block-coverage/ by default:

  • AI-Audit-Index.md — methodology, cross-repo summary and links
  • AI-Audit-Report.md — shared controls (ranked by impact) + core packs
  • Packs/AI-Audit-<PackName>.md — one file per pack in the additional roots

Harness operations write their outputs as sibling folders under audit/ (see audit-harness/).

See rw-build audit --help for all options and env var overrides.

Configuration

The packs directory can be configured via multiple methods (in priority order):

1. CLI Argument (highest priority)

rw-build all --packs ./my-elements

2. Environment Variable

RW_PACKS_DIR=./my-elements npm run build

3. package.json

{
"rw-elements-tools": {
"packsDir": "./my-elements"
}
}

4. Config File

// rw-elements-tools.config.jsexportdefault{packsDir: './my-elements'}

5. Default

If no configuration is provided, looks for ./packs in the project root.


Table of Contents

  1. Overview
  2. Directory Structure
  3. Properties Build System
  4. Shared Hooks Build System
  5. Controls
  6. Properties
  7. Configuration File Format
  8. Adding New Controls
  9. Adding New Properties
  10. Adding Shared Hooks
  11. Advanced Features
  12. Build Commands
  13. Programmatic Usage

Overview

The build system processes properties.config.json files located in element component directories and generates expanded properties.json files. This allows developers to:

  • Reuse common UI controls across multiple elements via globalControl references
  • Share property definitions (like spacing values, font weights) via use references
  • Override defaults per-element while maintaining a single source of truth
  • Apply theme defaults for consistent theming across elements

Data Flow

properties.config.json Controls (controls/)
│ │
▼ ▼
┌─────────────────────────────────────┐
│ build-properties.js │
│ • Expands globalControl references │
│ • Resolves 'use' property refs │
│ • Applies overrides & defaults │
│ • Injects Advanced group controls │
└─────────────────────────────────────┘
│
▼
properties.json
(consumed by RapidWeaver)

Directory Structure

rw-elements-tools/
├── bin/
│ └── cli.js # CLI entry point (rw-build command)
├── build-properties.js # Properties build script
├── build-shared-hooks.js # Shared hooks build script
├── config.js # Configuration resolver
├── index.js # Package entry point
├── package.json # npm package config
├── README.md # This documentation
├── controls/ # Reusable UI control definitions
│ ├── index.js # Exports all controls
│ ├── alignment/ # Flexbox/Grid alignment controls
│ ├── Animations/ # Animation and scroll animation controls
│ ├── Background/ # Background color, image, gradient, video
│ ├── Borders/ # Border and outline controls
│ ├── core/ # Essential controls (ID, CSSClasses, etc.)
│ ├── Effects/ # Box shadow, opacity, filters, blur
│ ├── grid-flex/ # Grid and flexbox item controls
│ ├── interactive/ # Button, input, link, filter controls
│ ├── Layout/ # Position, overflow, visibility, z-index
│ ├── Overlay/ # Overlay color, gradient, image
│ ├── Sizing/ # Width, height, min/max sizing
│ ├── Spacing/ # Margin and padding controls
│ ├── Transforms/ # Rotate, scale, skew, translate
│ ├── Transitions/ # Transition timing and properties
│ └── typography/ # Text color, decoration, styles
├── properties/ # Reusable property value definitions
│ ├── index.js # Exports all properties
│ ├── Slider.js # Slider value ranges
│ ├── FontWeight.js # Font weight options
│ └── ... # Other property definitions
└── shared-hooks/ # Shared JavaScript hook functions
├── animations/ # Animation and reveal functions
├── background/ # Background processing functions
├── borders/ # Border and outline functions
├── core/ # Essential utilities (classnames, etc.)
├── effects/ # Visual effects (opacity, filters)
├── interactive/ # Link and filter functions
├── layout/ # Layout and positioning
├── navigation/ # Navigation component styles
├── sizing/ # Dimensions and aspect ratios
├── spacing/ # Margin and padding functions
├── transforms/ # CSS transform functions
├── transitions/ # CSS and Alpine transitions
└── typography/ # Text and font style functions

Properties Build System

Entry Point

The properties build script is executed via:

cd src
npm run build

This runs node build.js which:

  1. Finds all config files matching the glob pattern:

    ../**/*.elementsdevpack/components/com.**/**/properties.config.json
    
  2. Processes each config file through the following pipeline:

    • Parse the JSON configuration
    • Set up the Advanced group with injected controls (CSSClasses, ID)
    • Process each property group
    • Expand globalControl references
    • Resolve use property references
    • Apply overrides and theme defaults
    • Write the output to properties.json

Processing Pipeline

For each property in a config file:

┌─────────────────────────────────────────────────────────────┐
│ 1. Check for globalControl │
│ └─ If present: Load control from Controls registry │
│ └─ If absent: Pass through with 'use' resolution only │
├─────────────────────────────────────────────────────────────┤
│ 2. Deep clone the control (avoid mutations) │
├─────────────────────────────────────────────────────────────┤
│ 3. Apply property overrides │
│ └─ String values: Replace directly │
│ └─ Object values: Shallow merge │
├─────────────────────────────────────────────────────────────┤
│ 4. Apply default values │
│ └─ Primitive defaults: Set control.default │
│ └─ Object defaults: Merge into theme properties │
├─────────────────────────────────────────────────────────────┤
│ 5. Apply theme defaults │
│ └─ themeColor, themeFont, themeBorderRadius, etc. │
├─────────────────────────────────────────────────────────────┤
│ 6. Transform IDs using {{value}} template │
│ └─ "prefix{{value}}Suffix" → "prefixControlIdSuffix" │
├─────────────────────────────────────────────────────────────┤
│ 7. Process nested globalControls recursively │
├─────────────────────────────────────────────────────────────┤
│ 8. Resolve 'use' references to Properties │
└─────────────────────────────────────────────────────────────┘

Shared Hooks Build System

The shared hooks build system combines reusable JavaScript utility functions with component-specific hook code, then tree shakes the result so each component ships only the shared hooks it actually uses.

Overview

shared-hooks/**/*.js Component hooks.source.js
│ │
▼ ▼
┌─────────────────────────────────────────┐
│ build-shared-hooks.js │
│ • Concatenates shared + component code │
│ • Tree shakes with esbuild │
│ • Keeps only code reachable from │
│ transformHook function │
└─────────────────────────────────────────┘
│
▼
hooks.js (tree shaken, unminified)
(consumed by RapidWeaver)

How It Works

  1. Find all source files: Scans packs/ for hooks.source.js files
  2. Read shared hooks: Loads all .js files from shared-hooks/ and its subfolders
  3. Concatenate: Combines shared code + component code
  4. Tree shaking: Uses esbuild to drop everything not reachable from transformHook
  5. Output: Writes hooks.js to each component — transpiled to es2018 CommonJS and left unminified, so it stays readable and greppable

Shared Hook Organization

Shared hooks are organized into category subfolders:

FolderPurposeExample Functions
core/Essential utilitiesclassnames, getHoverPrefix, globalHTMLTag
layout/Layout and positioningglobalLayout, globalActAsGridOrFlexItem
sizing/Dimensions and aspect ratiosglobalSizing, aspectRatioClasses
spacing/Margin and paddingglobalSpacing, globalSpacingMargin
background/Background stylesglobalBackground, globalBgImageFetchPriority
borders/Borders and outlinesglobalBorders, globalOutline
effects/Visual effectsglobalEffects, globalFilters, globalOverlay
typography/Text and font stylesglobalTextFontsAndTextStyles, globalHeadingColor
transforms/CSS transformsglobalTransforms
transitions/CSS/Alpine transitionsglobalTransitions, getAlpineTransitionAttributesMobile
animations/Animations and revealsglobalAnimations, globalReveal
navigation/Navigation stylesglobalNavItems, globalMenuItem
interactive/Links and filtersglobalLink, globalFilter

Component Hook Files

Each component that needs hooks creates a hooks.source.js file:

// packs/Core.elementsdevpack/components/com.realmacsoftware.button/hooks.source.jsfunctiontransformHook(rw){// Use any shared hook functions hereconstclasses=classnames(rw.props.customClasses).add(globalSpacing(rw)).toString();return{
classes
};}exports.transformHook=transformHook;

Key Points

  • Entry point: The transformHook function is the only exported function
  • Dead code elimination: Only code reachable from transformHook is kept
  • No manual imports: Shared code is concatenated, not imported
  • Auto-generated: Output hooks.js files are marked "do not edit"

Build Commands

# Build all hooks once
npm run build:hooks
# Watch for changes
npm run build:hooks:watch
# Using npm scripts
npm run build:hooks

Controls

Controls are reusable UI component definitions that map to RapidWeaver's property inspector UI elements.

Control Structure

A control is a JavaScript object (or array of objects) that defines:

// Simple control (single object)constFlexDirection={title: "Direction",id: "flexDirection",select: {default: "flex-col",items: [{value: "flex-col",title: "Column"},{value: "flex-row",title: "Row"},],},};exportdefaultFlexDirection;
// Compound control (array of objects)constLink=[{title: "Link",heading: {}},{title: "To",id: "globalLink",link: {}}];exportdefaultLink;

UI Element Types

Controls can use these UI element types:

TypeDescriptionExample
selectDropdown menu{ select: { default: "value", items: [...] } }
segmentedSegmented button control{ segmented: { default: "a", items: [...] } }
switchBoolean toggle{ switch: { default: false } }
sliderNumeric slider{ slider: { default: 50, min: 0, max: 100 } }
numberNumeric input{ number: { default: 0 } }
textText input{ text: { default: "" } }
textAreaMulti-line text{ textArea: { default: "" } }
inputCompact single-line input{ input: { default: "", subtitle: "" } }
linkLink picker{ link: {} }
collectionRepeating item editor (uses property instead of id){ collection: { identifier: "items" } }
resourceResource/file picker{ resource: {} } — see Resource accepts
headingSection heading (no value){ heading: {} }
dividerVisual separator{ divider: {} }
informationInfo text{ information: {} }
themeColorTheme color picker{ themeColor: { default: {...} } }
themeSpacingTheme spacing picker{ themeSpacing: { mode: "single" } }
themeBorderRadiusBorder radius picker{ themeBorderRadius: {...} }
themeBorderWidthBorder width picker{ themeBorderWidth: {...} }
themeShadowShadow picker{ themeShadow: {...} }
themeFontTheme font picker{ themeFont: { default: { base: { name: "heading" } } } }
themeTextStyleTheme text style picker{ themeTextStyle: { default: { base: { name: "lg" } } } }
themeTypographyTheme typography preset picker{ themeTypography: { default: { base: { name: "article" } } } }

Resource accepts

resource controls accept any file type by default. Restrict with accepts and excludes using HTML <input accept> token grammar:

TokenExampleMatches
Extension.svgFile extension
MIME wildcardimage/*, video/*MIME major type
Exact MIMEimage/svg+xmlExact MIME type

A file is allowed when at least one accepts token matches (or accepts is omitted) and no excludes token matches.

{
"title": "Photo",
"id": "photo",
"resource": {
"accepts": "image/*",
"excludes": ".svg"
}
}

Built-in controls apply sensible defaults: Image and background/overlay image controls use image/* excluding .svg; Background_SVG accepts .svg only; video controls accept video/*. Override on a globalControl reference when needed:

{
"globalControl": "Image",
"resource": { "accepts": "image/*" }
}

Control Properties

PropertyTypeDescription
titlestringDisplay label in the UI
idstringProperty identifier (used in templates)
formatstringCSS class format, e.g., "gap-x-{{value}}"
visiblestringVisibility condition, e.g., "otherProp == 'value'"
responsivebooleanWhether control is responsive (default: true)
globalControlstringReference to another control (nested)

Nested globalControls

Controls can reference other controls for composition:

constBorders=[{globalControl: "ControlType",id: "{{value}}Borders",},{globalControl: "BorderStyle",visible: "globalControlTypeBorders == 'static'",},{globalControl: "BorderColor",visible: "globalControlTypeBorders == 'static'",},];

Properties

Properties are reusable value definitions (like enums or option lists) that can be referenced using the use key.

Property Structure

// properties/FontWeight.jsconstFontWeight={default: "normal",items: [{value: "thin",title: "Thin"},{value: "light",title: "Light"},{value: "normal",title: "Normal"},{value: "medium",title: "Medium"},{value: "semibold",title: "Semibold"},{value: "bold",title: "Bold"},],};exportdefaultFontWeight;

Using Properties in Controls

Reference a property using the use key:

constTextWeight={title: "Weight",id: "textWeight",select: {use: "FontWeight"// Merges FontWeight's items and default}};

The build system will merge the referenced property, with local values taking precedence:

// Output{title: "Weight",id: "textWeight",select: {default: "normal",items: [{value: "thin",title: "Thin"},// ... etc]}}

Configuration File Format

properties.config.json Structure

{
"groups": [
{
"title": "Group Title",
"icon": "sf-symbol-name",
"properties": [
// Property definitions
]
}
]
}

Using globalControl

Reference a control by name:

{
"globalControl": "Spacing"
}

Overriding Control Properties

Add properties alongside globalControl to override:

{
"globalControl": "BorderRadius",
"title": "Corner Radius",
"default": {
"base": {
"topLeft": "lg",
"topRight": "lg",
"bottomLeft": "none",
"bottomRight": "none"
}
}
}

ID Templates with {{value}}

Transform the control's ID using a template:

{
"globalControl": "Spacing",
"id": "card{{value}}"
}

If the Spacing control has id: "globalPadding", the output becomes id: "cardGlobalPadding".

Theme Defaults

Override theme-related properties:

{
"globalControl": "Background_Color",
"themeColor": {
"default": {
"name": "brand",
"brightness": 500
}
}
}

Supported theme properties:

  • themeColor
  • themeFont
  • themeBorderRadius
  • themeBorderWidth
  • themeSpacing
  • themeShadow
  • themeTextStyle

Inline Properties

Properties without globalControl are passed through with use resolution:

{
"title": "Custom Width",
"id": "customWidth",
"slider": {
"use": "Slider",
"default": 100,
"min": 0,
"max": 500
}
}

Adding New Controls

Step 1: Create the Control File

Create a new file in the appropriate controls/ subdirectory:

// controls/Effects/NewEffect.jsconstNewEffect={title: "Effect Intensity",id: "effectIntensity",format: "effect-[{{value}}%]",slider: {default: 50,min: 0,max: 100,round: true,units: "%"}};exportdefaultNewEffect;

Step 2: Export from index.js

Add the export to controls/index.js:

// In the appropriate sectionexport{defaultasNewEffect}from"./Effects/NewEffect.js";

Step 3: Use in Config Files

Reference in any properties.config.json:

{
"globalControl": "NewEffect"
}

Step 4: Rebuild

cd src
npm run build

Creating Compound Controls

For controls with multiple UI elements:

// controls/interactive/CustomButton.jsconstCustomButton=[{title: "Button Settings",heading: {}},{title: "Style",id: "buttonStyle",segmented: {default: "solid",items: [{value: "solid",title: "Solid"},{value: "outline",title: "Outline"},{value: "ghost",title: "Ghost"}]}},{title: "Size",id: "buttonSize",select: {use: "ButtonSize"}}];exportdefaultCustomButton;

Creating Controls with Nested globalControls

// controls/Layout/CustomLayout.jsconstCustomLayout=[{globalControl: "ControlType",id: "{{value}}CustomLayout"},{visible: "globalControlTypeCustomLayout != 'none'",divider: {}},{visible: "globalControlTypeCustomLayout != 'none'",globalControl: "Position"},{visible: "globalControlTypeCustomLayout != 'none'",globalControl: "ZIndex"}];exportdefaultCustomLayout;

Adding New Properties

Step 1: Create the Property File

// properties/CustomSizes.jsconstCustomSizes={default: "md",items: [{value: "xs",title: "Extra Small"},{value: "sm",title: "Small"},{value: "md",title: "Medium"},{value: "lg",title: "Large"},{value: "xl",title: "Extra Large"},]};exportdefaultCustomSizes;

Step 2: Export from index.js

// properties/index.jsexport{defaultasCustomSizes}from"./CustomSizes.js";

Step 3: Use in Controls

constSizeSelector={title: "Size",id: "elementSize",select: {use: "CustomSizes"}};

Or use directly in config files:

{
"title": "Size",
"id": "mySize",
"select": {
"use": "CustomSizes"
}
}

Adding Shared Hooks

Step 1: Create the Shared Hook File

Create a new file in the appropriate shared-hooks/ subfolder:

// shared-hooks/effects/customEffect.js/** * Generate custom effect classes based on element properties * @param {Object} rw - The RapidWeaver element object * @returns {string} CSS class string */functioncustomEffect(rw){const{ customEnabled, customIntensity }=rw.props;if(!customEnabled)return'';returnclassnames(['custom-effect',customIntensity&&`intensity-${customIntensity}`]).toString();}

Step 2: Use in Component Hooks

Reference the function in any hooks.source.js:

// packs/MyPack.elementsdevpack/components/com.example.mycomponent/hooks.source.jsfunctiontransformHook(rw){// customEffect is available from shared hooks (no import needed)consteffectClasses=customEffect(rw);return{
effectClasses
};}exports.transformHook=transformHook;

Step 3: Build

npm run build:hooks

Naming Conventions

  • Folder organization: Place files in the appropriate category folder
  • Prefix with global: For element property processing functions (e.g., globalSpacing)
  • Use descriptive names: Match the function name to the file name
CategoryFolderExample
Core utilitiescore/classnames.js, getHoverPrefix.js
Layout functionslayout/globalLayout.js
Visual effectseffects/globalEffects.js
Typographytypography/globalHeadingColor.js

Tree Shaking

Every shared hook is concatenated into every component before the build narrows it down. esbuild then tree shakes from the exports.transformHook root: if you add a function to shared hooks but a component never reaches it, it won't appear in that component's hooks.js. In practice this removes roughly three quarters of the concatenated bytes.

Output is deliberately not minified — identifiers and formatting are preserved so generated hooks.js files stay readable, greppable, and produce meaningful git diffs.

This relies on shared hooks being statically analysable. Keep them as plain top-level const/function declarations with no top-level side effects, and avoid reaching for a helper by name at runtime (eval, new Function, or computed lookups like globalThis[name]) — esbuild can't see those references and will shake the helper away.

Example: Complex Shared Hook

// shared-hooks/layout/globalLayout.js/** * Generate layout-related CSS classes */constglobalLayout=(app,args={})=>{const{globalLayoutPosition: position,globalLayoutZIndex: zIndex,globalLayoutOverflow: overflow,}=app.props;returnclassnames([position,zIndex,overflow,]).toString();};

Advanced Features

Conditional Visibility

Show/hide controls based on other property values:

{
"title": "Custom Value",
"id": "customValue",
"visible": "sizeType == 'custom'",
"number": {
"default": 100
}
}

Complex conditions:

{
"visible": "enableFeature == true && mode == 'advanced'"
}

Format Strings

Generate CSS class names from values:

{
"id": "gapX",
"format": "gap-x-{{value}}",
"themeSpacing": {
"default": { "base": { "value": "4" } }
}
}

Output when value is "8": gap-x-8

Responsive Controls

By default, controls are responsive. Disable with:

{
"id": "staticValue",
"responsive": false,
"text": { "default": "" }
}

The Advanced Group

The build system automatically:

  1. Injects CSSClasses and ID controls at the start of the Advanced group
  2. Creates an Advanced group if one doesn't exist
  3. Moves any existing Advanced group to the end
  4. Sets the icon to "gearshape"

To add controls to the Advanced group:

{
"groups": [
{
"title": "Advanced",
"properties": [
{ "divider": {} },
{ "globalControl": "HTMLTag" }
]
}
]
}

Build Commands

Using the CLI (recommended)

# Build everything (properties + hooks)
rw-build all
# Build properties only
rw-build properties
# Build hooks only
rw-build hooks
# Watch for changes
rw-build all --watch # Watch both properties and hooks
rw-build properties --watch # Watch properties only
rw-build hooks --watch # Watch hooks only# Build with custom packs directory
rw-build all --packs ./my-elements
# Show help
rw-build --help

Using npm scripts

Add these to your package.json:

{
"scripts": {
"build": "rw-build all",
"build:properties": "rw-build properties",
"build:hooks": "rw-build hooks",
"dev": "rw-build all --watch"
}
}

Then run:

npm run build
npm run dev

Development Mode Details

The --watch flag monitors for changes and automatically rebuilds:

CommandWatches
rw-build all --watchBoth properties and hooks (runs watchers concurrently)
rw-build properties --watchproperties.config.json files in packs/
rw-build hooks --watchhooks.source.js files in packs/ and shared-hooks/*.js

Troubleshooting

"Global control 'X' not found"

  • Check the control is exported in controls/index.js
  • Verify the spelling matches exactly (case-sensitive)

"Property 'X' not found in Properties"

  • Check the property is exported in properties/index.js
  • Verify the use key spelling

Build produces unexpected output

  • Check for circular globalControl references
  • Verify JSON syntax in config files
  • Run with --trace-warnings flag for more details

Example: Complete Element Config

{
"groups": [
{
"title": "Content",
"icon": "text.alignleft",
"properties": [
{
"title": "Heading",
"id": "headingText",
"text": {
"default": "Welcome"
}
},
{
"globalControl": "HeadingColor"
}
]
},
{
"title": "Layout",
"icon": "square.split.bottomrightquarter",
"properties": [
{
"globalControl": "Layout"
}
]
},
{
"title": "Spacing",
"icon": "squareshape.squareshape.dotted",
"properties": [
{
"globalControl": "Spacing"
}
]
},
{
"title": "Background",
"icon": "paintbrush.fill",
"properties": [
{
"globalControl": "BackgroundTransparent"
}
]
},
{
"title": "Borders",
"icon": "square.dashed",
"properties": [
{
"globalControl": "Borders"
}
]
},
{
"title": "Advanced",
"properties": [
{
"divider": {}
},
{
"globalControl": "HTMLTag"
}
]
}
]
}

Programmatic Usage

You can also use rw-elements-tools programmatically in your own build scripts:

import{buildProperties,buildHooks,watchProperties,watchHooks,resolveConfig,Controls,Properties}from'rw-elements-tools';// Resolve configuration from all sourcesconstconfig=awaitresolveConfig({packs: './my-elements'// Optional CLI override});// Build propertiesawaitbuildProperties(config);// Build hooksawaitbuildHooks(config);// Watch for changesawaitwatchProperties(config);// Watch properties onlyawaitwatchHooks(config);// Watch hooks only// Or watch both concurrentlyawaitPromise.all([watchProperties(config),watchHooks(config)]);

Accessing Controls and Properties

import{Controls,Properties}from'rw-elements-tools';// Use a control definitionconsole.log(Controls.Spacing);console.log(Controls.BorderRadius);// Use a property definitionconsole.log(Properties.FontWeight);console.log(Properties.Slider);

Custom Build Pipeline

import{resolveConfig,buildProperties,buildHooks}from'rw-elements-tools';asyncfunctioncustomBuild(){constconfig=awaitresolveConfig();console.log('Building for:',config.packsDir);// Build properties firstawaitbuildProperties(config);// Then build hooksawaitbuildHooks(config);console.log('Build complete!');}customBuild();

Publishing

To publish the package to npm:

cd src
npm login
npm publish

For scoped packages:

npm publish --access public

Contributing

When adding new controls or properties:

  1. Follow naming conventions: PascalCase for exports, descriptive names
  2. Place in correct directory: Use the categorical structure
  3. Add exports: Update the relevant index.js
  4. Test the build: Run npm run build and verify output
  5. Document: Add comments for complex controls

Last updated: January 2026

About

Build tools for RW Elements packs

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages