Quick Start

CoCube combines reactive cells and expressions from spreadsheets, event-driven interactivity from web pages, and the visual workflow of a vector graphics design tool into a single system. It allows you to create, share, and collaborate on interactive local or collaborative tools like reactive diagrams, knowledge management systems, and more.

This guide gives an overview of building and using custom components.

Early access notes.

CoCube is in early access. Be aware that updates may change the behavior of existing components, though these changes are reduced to a minimum. The development updates section of the roadmap will contain instructions on how to fix your components when these updates happen. If you are met with a blank console screen, it is recommended to do a hard refresh.

CoCube pushes vector-rendering to its limit. This means a desktop browser with WebGPU support is required. Using Chrome is highly recommended.

Mobile browsers nearly support WebGPU, but this support is still unstable. This means CoCube's console can not yet be accessed on these devices.

Please join the Discord to ask questions, provide feedback, and report issues! CoCube has a small community; your feedback will be heard and acted on.

What is CoCube?

CoCube is a workspace for building composable tools. What does that mean? It means you can create and combine interactive components to build nearly any 2D vector-rendered software you can imagine. These components could be charts, calendars, tables, documents, and more. CoCube comes with a set of useful built-in components to start with. These components can be flexibly combined like Lego blocks to create useful tools.

Create a system.

The point of CoCube is to create useful tools and systems that you can use yourself. Combine built-in components to get started quickly. You can snap these components together to create nearly any tool you can imagine, from todo lists to complex note-taking systems to custom data visualizations.

CoCube gives you complete control to combine components however you want.

Work entirely locally.

Everything you create with CoCube is stored locally on your device. From building custom components to creating and using your system, you never have to share your data with CoCube's servers. This makes CoCube perfect for building e.g. private knowledge bases. When you need to work with others, you can enable sync and collaboration features on a per-component basis. This allows you to keep most of your data private while still enabling collaboration exactly where you need it.

Build custom components.

When the built-in components are insufficient, you can create your own components.

CoCube provides a simple, declarative language for building UI components, inspired by spreadsheets. This language is nowhere near as complex as, e.g., HTML, CSS, and JavaScript, but has nearly as much flexibility. The main feature of the language is allowing spreadsheet-like cells and expressions to define any attribute of a component, from its color to its behavior. Editing in this language is done with the component editor, which allows you to visually construct and modify a component in real-time. It is designed to make building reactive vector-rendered components as simple as possible.

Many tools similar to CoCube give you a set of opaque built-in components. Not here. Everything in CoCube, including every built-in component, is created in this simple but powerful language. Even the console, expression builders, and the component editor were created this way. From layout components like DynamicRow and DynamicColumn to text components like TextEditor, if a component doesn't work how you want it to, you can make your own alternative.

What is a component?

Components are composable visual elements you can combine, use, and create with CoCube. Like a spreadsheet, all attributes of a component exists in reactive cells. However, unlike a spreadsheet, a component is not displayed as a grid. Instead, the cells it contains define how it looks and behaves.

The following is a very simple component.

Height
100
Width
100

This component appears as a non-interactive green (default color) square (default shape) with a width and height of 100. Width and Height are the names of the two cells defining this component. As you will see, any cell can be defined as a value or as a spreadsheet-like expression, allowing you to easily create dynamic behavior.

Cells

Every cell in a component has a unique name, and contains an expression to compute its value. You can give any name you want to a cell, but two cells with the same name can not exist together in a component. In this guide, text that looks like Height, or Width is always referring to the name of a cell.

Some cells define attributes like layout, color, size, rotation, reactivity, and more. These cells are called attribute cells. They will be covered in the upcomming section.

Cells can be defined as a simple value, or as an expression. For example, the Width cell expects a Number. Instead of directly providing a value, you could set the cell to an expression that returns a Number. Something like:

Add
2
3

An expression that does not return the expected type of data will result in the Width cell taking its default value of 10.

In this guide, text formatted like Number, Color, or Bool indicates the type of some data. Usually the type is self-explanatory, Number is a number, Text is some text string, like "hello". A Bool can be either true or false. An Array is a list of values, for example:

[1,"hi",true]

A Map is a set of named values, for example:

"Item"
"Bubblegum"
"Cost"
3

Additional types like Color, EventHandler, Anchor, and Component, and others will be covered later in the guide.

Attribute Cells

Attribute cells are used to define how the component looks and behaves. These are cells with special names, and are listed in the table below along with the type of data they expect.

Color Color

Color of the component.

Width Number

Width of the component.

Height Number

Height of the component.

Rotation Angle

Rotation angle of the component.

RotationAbsolute Bool

Set to prevent a component from inheriting its parent rotation.

Display Shape

Define what the component looks like (square, letter, etc).

AlignTo FixPoint

Set where the component aligns to.

AlignFrom FixPoint

Set the point on the component which should act as the center.

Anchor Anchor

Define what the component is aligned to.

Animation Map

Define the animations for the component.

EventHandler EventHandler

Define how the component reacts to events.

Components Map

Define subcomponents.

Selectable Bool

If true, the component will be selectable by the event system.

PivotX Number

Define the pivot point offset in the X direction.

PivotY Number

Define the pivot point offset in the Y direction.

OffsetX Number

Define the component offset in the X direction.

OffsetY Number

Define the component offset in the Y direction.

ClipMask Bool

If true, the component will act as a clip mask. Subcomponents will only render directly behind it.

Layer Bool

If true, the component will be displayed above all other components.

Creating a component.

Navigate to the console.

Click the Create Component button to create and start editing a new component.

With the editor open, you should see a single component rendered as a green square. This is how a component looks when no cells are defined.

See Create: Interactive Bar Chart for a step-by-step walkthrough of creating an interactive chart component from scratch.

Modifying appearance.

Let's change the color of the new component.

Create a new cell with the name Color. Capitalization matters when naming cells, Color is different from color. When naming the cell you want to insert you should also see a popup with a few common cell names. These are attribute cells - the cells which change the appearance or behavior of the component. Feel free to select Color from the list instead of typing it out.

When the cell is created, it will contain a default value. To change this value, click the cell itself in the cells panel. This will open the expression editor for that cell.

Since we are editing the Color cell, we want the cell to contain a Color, not a Number, Text, or any other type of data. You can change the type of data using the expression editor, by clicking on the type dropdown and selecting Color. Once the Color cell contains a Color you should see your component change.

Combining components.

Let's say you wanted to add a component to another component. This is where the Components attribute cell comes in. The following is a definition of a component containing two subcomponents named Little and Big.

Components
"Little"
Height
20
Width
20
"Big"
Height
80
Width
80
Height
50
Width
50

The easiest way to add new components like this is to use the Components panel. Alternatively, you can just set the Components cell yourself. This cell expects a Map containing Components and/or Views.

After creating a component, you can then add it as a subcomponent to another component using a View.

Nested Component Layout

This section looks at how a component is aligned relative to its containing component.

By default, the center of a subcomponent is aligned to the center of the parent component. This can be adjusted in multiple ways.

The first method is to set the AlignTo and AlignFrom cells.

You can also set the OffsetX and OffsetY cells to add some additional offset to the component. These cells expect a Number.

The PivotX and PivotY cells allow you to adjust the rotational center point of the component. These cells expect a Number.

The Anchor cell allows you to change what the component is relatively anchored to. By default, subcomponents are anchored to their containing component. This can be changed to anchor a component to the screen, or to the workspace. This cell expects an Anchor.

Overriding cells.

Overriding cells is what allows generic components to be customized for a given use-case.

Selecting a component in the Components panel opens the editor on that component. If the subcomponent is included as a reference, then modifying its cells while it is selected just overrides the cell for that specific reference.

Selecting a child component that is defined directly (e.g. is a Component not a View) allows you to edit the nested component directly - NOT setting overrides.

Cells and references.

Cells can reference other cells via a few functions.

Here is a component that uses a reference to compute its size dynamically:

Height
Cell
"Width"
Width
50

Modifying the Width cell would cause the Height of the component to change as well; in this case, the height will always match the width.

To read a cell in a parent component use e.g.:

ParentCell
"Width"
1

This function reads the Width of the containing component. For example:

Components
"A"
Height
ParentCell
"Width"
1
Width
50
Width
100

In this component, any change to the containing components Width cell will result in a change to the subcomponents Height.

Adding reactivity.

Components can be configured to react to events.

To make a component reactive, set the EventHandler cell to an EventHandler.You can then configure the event handler inside the expression editor.

Event handlers work using two concepts: Matchers and Actions.

A matcher allows you to choose what events the component will listen to. For example, if a cell should be modified when the component is clicked, then the matcher would be:

Mouse Left Press

Once a matcher is selected, actions can be added. Say the components Color cell should be changed. To do this, add the following action to the event handler:

SetCell Color

The matcher would then trigger the action when the component is clicked, changing its color.

SetCell can take any expression, and that expression is evaluated when it matches an event.

Every matcher can have any number of actions associated with it.

You will also see an option to either bubble or capture the matched event. Capturing the event means the event will not propagate to parent components after the actions have fired in this handler. Bubbling the event means the matched event will be propagated to parent components, potentially to be matched on again.

Understanding emit tags and emitted data.

It is a common occurrence that you will want to emit a custom event from a component. You can do this using the Emit action.

This action takes two arguments.

  • An expression which is evaluated to an event tag (Text).
  • An expression which is evaluated to any data you want associated with the event.

You can then define a matcher to match on the tag from the custom event. Use the Matched function to retrieve the matched data in an action.

Collaboration and publishing.

Once you have something you want to publish just make it public and share the URL.