Code generation is a core feature of Plasmic; it is key to having a single source of truth between design and code.
There are a lot of ways to generate code from designs; here are the guiding principles that we follow to inform our decisions:
You can update components with new designs. Plasmic-generated code must have a persistent connection to design. Once you have attached behavior to Plasmic-generated components, it must still be possible to update the design of those components, and merge in newly-generated code without losing any of your existing work. This is in stark contrast to one-time code export tools, where once code is generated and edited for production, it can no longer be updated with new designs. With Plasmic, design and code are always connected through the lifetime of that component, not just in an initial code export.
You have total control over component props. The fact that a component is rendered by Plasmic should just be an implementation detail, and someone should be able to use a Plasmic-rendered component without even knowing what Plasmic is. In effect, this means giving you all the flexibility you need to curate the interface for your components exactly as you see fit. We do not want to litter your component props with all sorts of Plasmic-specific concerns or knowledge; instead, they should be encapsulated within your component.
You can instrument components to do anything. It must be possible for you to implement all the logic and behavior you want in a Plasmic-rendered component. You must be able to attach event handlers, fetch and render real production data, use React hooks and state, and everything else you expect to be able to do if you were writing the component from scratch. We make it easy to stay true to the design, but ultimately, you must have total control over what gets rendered to screen.
We offer two different codegen schemes: the Blackbox Library scheme and the Direct Edit scheme.
Plasmic generates files that are used as blackbox libraries by your React components to render things exactly as they were designed in Plasmic. There is a clear separation between files owned by Plasmic — in charge of presentation — and files owned by you — in charge of behavior. When the design of components are updated, the Plasmic-generated files are overwritten with the new designs.
Plasmic generates component files that are jointly-owned by Plasmic and by you. From within the same file, Plasmic controls the styling and structure of the React tree, but you make edits to them to attach event handlers and add state. When the design of components are updated, the Plasmic attempts to intelligently merge the new designs and your edits in the same file.
Which scheme should you use?
The two schemes are similarly expressive, but we generally recommend defaulting to the Blackbox scheme. You can check out this comparison of the two schemes here.
It is also possible to switch between the two schemes on a per-component basis by editing
plasmic.json; see here for details.
For styling, we generate plain CSS that is then imported into the React components. Each component has a corresponding css file, and each element in that component has its own css class defined. If the component has multiple variants, there will also be multiple classes corresponding to different variants for each affected element.
In browsers today, certain (mostly typographic) styles are inherited / cascaded, and elements come with a default set of styles (e.g. buttons are pre-styled to look very different from a
In Plasmic, by default, each component fully specifies its own styles; we block css inheritance at component boundaries. This insulates the look and feel of each component from the browser’s defaults and from inheritance. This deterministic styling yields more predictable results, and guarantees that the final app faithfully reflects the design.
For instance, if you drop a button into a header, this insulation helps ensure the button won’t suddenly have large type just because of where it is placed. Similarly, it prevents the styles you already have in your app from getting inherited into and affecting the appearance of Plasmic components that you drop into the app.
Lastly, this approach allows the UI you design in Plasmic to be cross-platform—feasible on platforms other than the web, which is unique in its treatment of inheritance.
In Plasmic, rather than directly expose users to the raw CSS layout properties like
flex-basis, etc., we provide some simpler layout abstractions. This is for a few reasons:
- To distill the complex space of possibilities into a more compact and intuitive set of controls. CSS layout can be tricky to understand and debug even for seasoned web developers, let alone designers accustomed to direct manipulation tools.
- To ensure that the layouts one can express in Plasmic can be executed across different platforms other than the web.
Layout is accomplished using boxes, which are containers. There are currently two types of boxes in Plasmic:
- Horizontal and vertical stacks correspond to flexbox layouts. By default their children are auto-positioned within the flex flow, but can also include “free-floating” (absolutely positioned) children. These are generally the most common type of container in a UI that is meant to go into production.
- Free blocks contain only free-floating children. These are esp. handy for exploratory design when just wanting to draw things. These are anchored to the top/left/bottom/right.
Stacks support specifying a default gap between the children items. This is implemented as a polyfill for the
gap property coming to CSS. Margins that individual children specify for themselves are added to this gap. Besides the gap along the main axis, a cross-axis gap can also be specified (separating rows in a horizontal stack, for instance).
Plasmic also generates code for: