Basic Tutorial
#
OverviewIn this tutorial, we'll be designing a simple page editor. It's recommended that you have a basic to intermediate workings of React and it'd be even better if you first have a quick glance at the Core Concepts and come back here. If you are feeling adventurous, that's fine too.
#
Installationor with npm:
#
Designing a user interfaceWith Craft.js you decide how your editor should look and function. So, let's build a user interface for our page editor. We'll add the page editor functionalities later.
To make our lives easier, we'll use some external packages for designing our user interfaces.
#
User ComponentsLet's first create the User Components - the components that our end users will be able create/edit/move around.
#
Text#
Button#
ContainerWe will also create a Container component to allow our users to change its background colour and padding.
#
CardNow, let's create another user component that will be more advanced. It will be composed of the Container component we made earlier, and it will contain two droppable regions; one for text and another for buttons.
#
The Editor#
ToolboxLet's build a "toolbox" which our users will be able to drag and drop to create new instances of those User Components we just defined.
#
Settings PanelWe also want to create a section here where we can display a bunch of settings which our users can use to edit the props of the user components.
For now, let's just put in some dummy text fields. We'll revisit this in the later sections.
#
Top barLet's design a section that is going to contain a switch for users to disable the editor's functionality and also a button that is simply going to display the serialized output in the browser's console.
#
Putting it all togetherNow, let's put together our entire React application.

#
Implementing Craft.jsUp to this point, we have made a user interface for our page editor. Now, let's get it to work!
#
Setup- First wrap our application with
<Editor />
which sets up the Editor's context. We'll also need to specify the list of user components in theresolver
prop for Craft.js to be able to (de)serialize our User Components. - Then wrap the editable area with
<Frame />
which passes the rendering process to Craft.js.
Every element that is rendered in <Frame />
is managed by an object in the editor's internal state called a Node
which describes the element, its events, and props among other things.
Whether an element is draggable or droppable (or neither) depends on the type of Node
that manages it.
- If the
Node
is a Canvas, then it's droppable - If the
Node
is an immediate child of a Canvas, then it's draggable.
By default, every element inside the <Frame />
will have a non-Canvas Node automatically defined for it:
Hence, by default, all the Nodes above are neither draggable nor droppable. So how can we define some of the Nodes above as a Canvas Node?
We can use the provided <Element />
component to manually define Nodes:
In the above code, we've wrapped our Container
components with <Element />
with the canvas
prop, thus making the component droppable and its immediate children, draggable.
Once you've applied these changes and refresh the page, you will notice that absolutely nothing has changed - and that's a good thing!
#
Enabling Drag and DropInside a User Component, we have access to the useNode
hook which provides several information and methods related to the corresponding Node
.
The first thing we will need to do is to let Craft.js to manage the DOM of our component. The hook provides connectors
which act as a bridge between the DOM and the events in Craft.js:
Let's break this down a little:
- We passed the
connect
connector to the root element of our component; this tells Craft.js that this element represents the Text component. If the component's corresponding Node is a Canvas, then this also defines the area that is droppable. - Then, we also passed
drag
connector to the same root element; this adds the drag handlers to the DOM. If the component's Node is a child of a Canvas, then the user will be able to drag this element and it will move the entire Text component.
We can also specify additional configuration to our component via the craft
prop. Let's define drag-n-drop rules for our Text Component:
Our Text component can now only be dragged if the text
prop is not set to "Drag" ๐คช
Nice, now let's enable drag-n-drop for the other User Components:
At this point, you could refresh the page and you would be able to drag stuff around.

#
Defining Droppable regionsOf course, our Card component is supposed to have 2 droppable regions, which means we'll need 2 Canvas nodes.
But hold up, how do we even create a Node inside a User Component? Remember the <Element />
component that was used to define Nodes earlier in our application? Well it can be used here as well.
<Element />
used inside User Component must specify anid
prop
You might be wondering how do we set drag/drop rules for the new droppable regions we made. Currently, we have set the is
prop in our <Element />
to a div, but we can actually point it to a User Component.
Hence, we can specify and create a new User Component and define rules via the craft
prop just like what we have done previously.
Remember that every User Component must be added to our resolver, so let's add CardTop and CardBottom:

#
Implementing the ToolboxLet's go back to our Toolbox component and modify it so that dragging those buttons into the editor will create new instances of the user components they represent. Just as useNode
provides methods and information related to a specific Node
, useEditor
specifies methods and information related to the entire editor's state.
The useEditor
also provides connectors
; the one we are interested in right now is create
which attaches a drag handler to the DOM specified in its first argument and creates the element specified in its second arguement.
Notice for our Container component, we wrapped it with the <Element canvas />
- this will allow our users to drag and drop a new Container component that is droppable.
Now, you can drag and drop the Buttons, and they will actually create new instances of our User Components.
#
Making the components editableUp until this point, we have a page editor where our users can move elements around. But, we are missing one important thing - enabling our users to edit the components' props.
The useNode
hook provides us with the method setProp
which can be used to manipulate a component's props. Let's implement a content editable for our Text Component:
For simplicity's sake, we will be using react-contenteditable
But let's only enable content editable only when the component is clicked when it's already selected; a double click is essential.
The useNode
hook accepts a collector function which can be used to retrieve state information related to the corresponding Node
:

This should give you an idea of the possibilities of implementing powerful visual editing features like what you'd see in most modern page editors.
While we are at it, let's also add a slider for users to edit the fontSize

We can agree that it does not look all that good since it obstructs the user experience. Wouldn't it be better if the entire .text-additional-settings
Grid is relocated to the Settings Panel that we created earlier?
The question is, how will the Settings Panel be able render the .text-additional-settings
when our Text component is selected?
This is where Related Components become useful. Essentially, a Related Component shares the same Node
context as our actual User component; it can make use of the useNode
hook. Additionally, a Related Component is registered to a component's Node
, which means we can access and render this component anywhere within the editor.
Before we move on to the Settings Panel, let's quickly do the same for the other User Components:
#
Setting default propsSetting default props is not strictly necessary. However, it is helpful if we wish to access the component's props via its corresponding Node
, like what we did in the settings
related component above.
For instance, if a Text component is rendered as <Text text="Hi" />
, we would get a null value when we try to retrieve the fontSize
prop via its Node
. An easy way to solve this is to explicity define each User Component's props
:
#
Settings PanelWe need to get the currently selected component which can be obtained from the editor's internal state. Similar to useNode
, a collector function can be specified to useEditor
. The difference is here, we'll be dealing with the editor's internal state rather than with a specific Node
:
Note: state.events.selected is of type
Set<string>
. This is because in the case of multi-select, it's possible for the user to select multiple Nodes by holding down the<meta>
key.
Now, let's replace the placeholder text fields in our Settings Panel with the settings
Related Component:
Now, we have to make our Delete button work. We can achieve this by using the delete
action available from the useEditor
hook.
Also, it's important to note that not all nodes are deletable - if we try to delete an undeletable Node, it'll result in an error. Hence, it's good to make use of the helper methods which helps describe a Node. In our case, we would like to know if the currently selected Node is deletable before actually displaying the "Delete" button. We can access the helper methods via the node
query in the useEditor
hook.

#
TopbarThis is the last part of the editor that we have to take care of and then we're done!
First, we can get the editor's enabled
state by passing in a collector function just like what we did before. Then, we can use the setOptions
action to toggle the enabled
state.
Lastly, the useEditor
hook also provides query
methods which provide information based the editor'state. In our case, we would like to get the current state of all the Nodes
in a serialized form; we can do this by calling the serialize
query method.

We'll explore how to compress the JSON output and have the editor load from the serialised JSON in the Save and Load guide.
#
You made it ๐We've made it to the end! Not too bad right? Hopefully, you're able to see the simplicity of building a fully working page editor with Craft.js.
We do not need to worry about implementing the drag-n-drop system but rather simply focus on writing rules and attaching connectors to the desired elements.
When it comes to writing the components themselves, it is the same as writing any other React component - you control how the components react to different editor events and how they are edited.