# Structure
# Node Types
The building block of the UIDL structure is the UIDLNode
. Each node has the same structure:
{
type: string
content: any
}
Available types are: static
, dynamic
, element
, conditional
, repeat
, slot
and nested-style
.
As the UIDL is being traversed, the nodes are interpreted and translated into lines of code:
static
nodes become plain textdynamic
nodes become expressions inside templateselement
nodes become tags- and so on ...
The content represents all the information that the current node
holds. You can read more about the structure of each node below. We will refer to a UIDLNode
whenever a field in the UIDL allows any node type as a value.
Note that not all node types are intended to be used everywhere. Some types are restricted to certain keys in the UIDL. Please consult the table below for a full understanding of the usage:
Root Node | Element Children | Attribute | Style | Conditional Ref | Repeat Source | |
---|---|---|---|---|---|---|
Static Value | x | x | x | x | x | |
Dynamic Ref | x | x | x | x | x | x |
Element Node | x | x | ||||
Conditional Node | x | x | ||||
Repeat Node | x | x | ||||
Slot Node | x |
# Static Value
Static node types are static values which are meant to be treated as strings or numbers to be passed on by the code generators as they are.
interface UIDLStaticValue {
type: "static";
content: string | number | boolean;
}
The height: 100px
style value is a good example of a static value. Also, the content of the text
element is Hello World
, as a static string.
{
"name": "Message",
"node": {
"type": "element",
"style": {
"height": {
"type": "static",
"content": "100px"
}
},
"content": {
"elementType": "text",
"children": [
{
"type": "static",
"content": "Hello World"
}
]
}
}
}
# Dynamic Reference
A dynamic node can be used when you need to reference a value that will be supplied at runtime by the generated code. You can think of component internal state
or component props
, but also local
variables.
interface UIDLDynamicReference {
type: "dynamic";
content: {
referenceType: "prop" | "state" | "local" | "attr" | "children" | "token";
id: string;
};
}
In this case, the content can have the following fields:
referenceType
- Identifies the type of dynamic reference (ex: 'prop', 'state', 'local', 'attr', 'children', 'token')id
- Identifies a specific value from the dynamic object (ex: 'isVisible', 'title')
TIP
The id
field supports the dot notation, ex: 'user.name'
Such dynamic values are usually declared at the component level root. Check prop definitions and state definitions for that.
An example of using a dynamic prop
in an attribute:
{
"propDefinitions": {
"authorAvatarUrl": {
"type": "string"
}
},
"name": "ImageElement",
"node": {
"type": "element",
"content": {
"elementType": "image",
"attrs": {
"url": {
"type": "dynamic",
"content": {
"referenceType": "prop",
"id": "authorAvatarUrl"
}
}
}
}
}
}
# Element Node
When you want to represent a visual element (ex: input, image), you can use an UIDLElementNode. The generators will take these elements and will map them to the specific platform (ex: container
will become div
, image
will become img
).
interface UIDLElementNode {
type: "element";
content: {
elementType: string;
semanticType?: string;
name?: string;
key?: string; // internal usage
dependency?: UIDLDependency;
style?: UIDLStyleDefinitions;
attrs?: Record<string, UIDLAttributeValue>;
events?: UIDLEventDefinitions;
abilities?: {
link?: UIDLLinkNode;
};
referencedStyles?: UIDLReferencedStyles;
children?: UIDLNode[];
selfClosing?: boolean;
ignore?: boolean;
};
}
In this case, the content can have the following fields:
elementType
- the type of the abstract element (ex: 'container', 'text', 'image', etc.)name
- each element can have a custom name. As a fallback the elementType is used.dependency
- adds information about the element if it is a custom component or something used from an external packagestyle
- defines the visual aspect of the element, with css-like properties. Each key is the name of the attribute, each value is of typestatic
,dynamic
.attrs
- defines any properties/attributes added on this element. For custom elements, the attributes will be translated into dynamic values inside. Each key is theevents
- defines a list of instructions that can be added on event handlers. This is an experimental feature and has limited capabilities for now.children
- is the array ofUIDLNode
objects that this element surrounds. Using this field, we ensure the tree-like structure of the entire component.referencedStyles
- is used to refer to styles from project-style sheet or defining media styles for a node.
This is how you define an image element
:
{
"type": "element",
"content": {
"elementType": "image",
"attrs": {
"url": "path/to/avatar.jpg"
}
}
}
The element node can contain other element nodes as children, and the elementType
must exist either in the mappings used by the generator or it should be defined as one of the components of the project.
TIP
Notice the composition pattern between two elements.
{
"name": "ImageElement",
"node": {
"type": "element",
"content": {
"elementType": "container",
"children": [
{
"type": "element",
"content": {
"elementType": "image",
"attrs": {
"url": {
"type": "static",
"content": "path/to/avatar/url"
}
}
}
}
]
}
}
}
When run through the React
generator, this will yield:
import React from "react";
const ImageElement = (props) => {
return (
<div>
<img src="path/to/avatar/url" />
</div>
);
};
export default ImageElement;
# Conditional Node
This node should be used when an UIDLNode should be rendered inside a conditional expression (ex: v-if in Vue).
interface UIDLConditionalNode {
type: "conditional";
content: {
node: UIDLNode;
reference: UIDLDynamicReference;
value?: string | number | boolean;
condition?: {
conditions: Array<{
operation: string;
operand?: string | boolean | number;
}>;
matchingCriteria?: string;
};
};
}
The content node contains:
node
- the instance of UIDLNode which is placed behind the conditionalreference
- a UIDLDynamicReference value based on which the rendering condition is workingvalue
- the value of the dynamic reference for which thenode
is displayedcondition
- the explicit conditional expression based on which thenode
is displayed
The conditional node will either use the value
and make an equality (===) between the reference
and the value
, or can use the condition
, which allows for more complex conditionals, using all the available binary and unary operators.
A condition like:
{
"conditions": [
{ "operation": ">", "operand": 3 },
{ "operation": "<=", "operand": 5 }
],
"matchingCriteria": "all"
}
will render into:
reference > 3 && reference <= 5;
In the following example, you can see a conditional node, based on the true
/false
value of a state
key.
{
"name": "MyConditionalElement",
"stateDefinitions": {
"isVisible": {
"type": "boolean",
"defaultValue": true
}
},
"node": {
"type": "element",
"content": {
"elementType": "div",
"children": [
{
"type": "conditional",
"content": {
"reference": {
"type": "dynamic",
"content": {
"referenceType": "state",
"id": "isVisible"
}
},
"value": true,
"node": {
"type": "element",
"content": {
"elementType": "text",
"children": [
{
"type": "static",
"content": "Now you see me!"
}
]
}
}
}
}
]
}
}
}
# Repeat Node
A common pattern in front end development is mapping multiple entities of the same type, usually provided in a data array, to a set of identical or similar visual elements.
This node allows you to represent a node inside a repetitive structure (ex: v-for in Vue).
interface UIDLRepeatNode {
type: "repeat";
content: {
node: UIDLNode;
dataSource: UIDLAttributeValue;
meta?: {
useIndex?: boolean;
iteratorName?: string;
dataSourceIdentifier?: string;
};
};
}
The content allows the following fields:
node
- the UIDLNode that will be placed inside the repeaterdataSource
- the array of values over which the code iteratesmeta.useIndex
- when this flag is present, the iteration declares theindex
value as the position of the element in the arraymeta.iteratorName
- a string which overrides the name of the variable inside the iteration (default:item
)meta.dataSourceIdentifier
- a string which identifies the local data source variable inside the component. This is used only when you are passing a static array as a dataSource and the framework needs to declare that array as a local variable (ex: Vue will place this on thedata
object)
A repeat over an array retrieved from props
:
{
"name": "MyRepeatElement",
"propDefinitions": {
"items": {
"type": "array",
"defaultValue": ["hello", "world"]
}
},
"node": {
"type": "element",
"content": {
"elementType": "div",
"children": [
{
"type": "repeat",
"content": {
"node": {
"type": "element",
"content": {
"elementType": "text",
"children": [
{
"type": "dynamic",
"content": {
"referenceType": "local",
"id": "item"
}
}
]
}
},
"dataSource": {
"type": "dynamic",
"content": {
"referenceType": "prop",
"id": "items"
}
},
"meta": {
"useIndex": true,
"iteratorName": "item"
}
}
}
]
}
}
}
This will yield the following component when using the Vue
generator:
<template>
<div>
<span v-for="(item, index) in items" :key="index">{{ item }}</span>
</div>
</template>
<script>
export default {
name: "MyRepeatElement",
props: {
items: {
type: Array,
default: ["hello", "world"],
},
},
};
</script>
# Slot Node
WARNING
This is not stable yet and is subject to changes in the near future
This node type is exclusive to arrays of children in element nodes. Because a component can have some children declared inline and other children passed from parents we need a way to specify where these parent-provided-children get to be placed in relation with the other elements of the component. The concept of slot from web components allows us to do exactly this.
{
"name": "MySlotElement",
"node": {
"type": "element",
"content": {
"elementType": "div",
"children": [
{
"type": "static",
"content": "static header"
},
{
"type": "slot",
"content": {}
},
{
"type": "static",
"content": "static footer"
}
]
}
}
}
We plan to handle name slots in the future, but for now only the default slot is supported.
# Styles
Styles can be defined on Eelement Node (opens new window) using style
and
referencedStyles
attributes. style
attribute is used to define styles that directly reflects on the node. Style can be defined using static node.
{
"type": "element",
"content": {
"elementType": "container",
"children": [
{
"type": "element",
"content": {
"elementType": "text",
"style": {
"width": "100px"
},
"children": [
{
"type": "static",
"content": "World!"
}
]
}
}
]
}
# Referenced Styles
referencedStyles
are used for defining media
and element-state
using inlined
. Styles from project-referenced
are refered using
project-referenced
flag. More details on how to use and refer styles are mentioned here (opens new window).
type UIDLReferencedStyles = Record<string, UIDLElementNodeReferenceStyles>;
type UIDLElementNodeReferenceStyles =
| UIDLElementNodeProjectReferencedStyle
| UIDLElementNodeInlineReferencedStyle;
Element Node
with referencedStyles
looks like
<!-- other UIDLElementNode fields --->
"referencedStyles": {
"5ed0d3daf36df4da926078e": {
"id": "5ed0d3daf36df4da926078e",
"type": "style-map",
"content": {
"mapType": "project-referenced",
"referenceId": "5ed0d4923de727e93cb4efa2"
}
},
"5ecfa0d2f9f29ada8482ff03": {
"id": "5ecfa0d2f9f29ada8482ff03",
"type": "style-map",
"content": {
"mapType": "inlined",
"conditions": [
{ "conditionType": "element-state", "content": "hover"}
],
"styles": {
"color": { "type": "static", "content": "red"},
"border-bottom": "3px solid red",
"padding-bottom": "7px"
}
}
}
}
<!-- other UIDLElementNode fields --->
# Component UIDL
When building modern interfaces, a component represents a reusable piece of code. In the realm of the UIDL, a component represents a set of tree-like UIDLNodes together with some top level declarations used to identify the dynamic data inside.
interface ComponentUIDL {
name: string;
node: UIDLElementNode;
styleSetDefinitions?: Record<string, UIDLStyleSetDefinition>;
propDefinitions?: Record<string, UIDLPropDefinition>;
importDefinitions?: Record<string, UIDLExternalDependency>;
peerDefinitions?: Record<string, UIDLPeerDependency>;
stateDefinitions?: Record<string, UIDLStateDefinition>;
outputOptions?: UIDLComponentOutputOptions;
designLanguage?: {
tokens?: UIDLDesignTokens;
};
seo?: UIDLComponentSEO;
}
The fields that can be used at the component root level:
name
- unique string name identifier of the component. The name is used for generating the component name, but can also represent the file name when used in a project generation process.node
- Any instance ofUIDLNode
which becomes the root node of the rendered component.
Additionally, depending on the context you can use one of the following optional fields:
meta
- object containing dynamic values, also used at other levels throughout the UIDL.stateDefinitions
- object containing information used to define the state of a component. For more details about props definition structure check below the State Definitions dedicated section.propDefinitions
- object with information used as a content for the component. For more details about state definition structure check below the Prop Definitions dedicated section.
A basic component consisting of a single text
element with some static value inside can be represented like this:
{
"name": "Message",
"node": {
"type": "element",
"content": {
"elementType": "text",
"children": [
{
"content": "Hello World!!",
"type": "static"
}
]
}
}
}
which would yield (in Vue):
<template>
<span>Hello World!!</span>
</template>
<script>
export default {
name: "Message",
};
</script>
A more complex example of a UIDL component would be this AuthorCard
:
{
"name": "AuthorCard",
"propDefinitions": {
"title": {
"type": "string",
"defaultValue": "Hello"
}
},
"node": {
"type": "element",
"content": {
"elementType": "container",
"attrs": {
"data-static-attr": {
"type": "static",
"content": "test"
},
"data-dynamic-attr": {
"type": "dynamic",
"content": {
"referenceType": "prop",
"id": "title"
}
}
},
"children": [
{
"type": "element",
"content": {
"elementType": "text",
"children": [
{
"type": "static",
"content": "Hello World!"
},
{
"type": "dynamic",
"content": {
"referenceType": "prop",
"id": "title"
}
}
]
}
}
]
}
}
}
TIP
As you can see, a component can describe an entire tree of subcomponents that work together to build the user interface required for a given functionality.
TIP
For more information about the types of children and values a component can have, check the node types section of the docs.
# Prop Definitions
Component properties act like the public interface of each component. Using props, the parent component can pass any value to any of its children. A component must define its props via propDefinitions
in order to be able to use them safely.
Each prop definition has to follow this interface:
interface UIDLPropDefinition {
type: string
defaultValue?: string | number | boolean | ...
isRequired?: boolean
meta?: Record<string, any>
}
The UIDL prop definitions are structured in an object where the name of the prop
is the key
and the value
is a UIDLPropDefinition
. Inside the prop definition you can set:
type
- the type of the prop (ex: string, number, boolean, object, etc.)defaultValue
- the value used by the components when the prop is not providedisRequired
- a boolean flag indicating if the prop is marked as required in the generated codemeta
- additional info which can be sent to the component generators
Sample example of propDefinitions:
{
<!--- other UIDL fields -->
"propDefinitions": {
"title": {
"type": "string",
"defaultValue": "Hello"
},
"items": {
"type": "array",
"defaultValue": []
},
"isShareable": {
"type": "boolean",
"defaultValue": false
},
"isDisplayed": {
"type": "boolean",
"defaultValue": true
}
}
<!--- other UIDL fields -->
}
# State Definitions
Components can declare state keys as internal values, which are encapsulated inside them. There are a couple of experimental instructions which can be used to declare changes to the state at runtime. State values are regularly used to render conditional
or repeat
nodes.
Similarly to propDefinitions, stateDefinitions
is an object, where the key
is the name of the state
and the value
is of type UIDLStateDefinition
:
interface UIDLStateDefinition {
type: string
defaultValue: string | number | boolean | ...
values?: Array<{
value: string | number | boolean
meta?: { ... }
}>
}
where:
type
- represents the type of the state (ex: string, number, boolean, object, array etc.)defaultValue
- is the initial state valuevalues
- is an array of exact values that the state can be in. This is used for now when defining the routing for projects. In the future, this would be the basis for defining more complex state transitions.
A sample of stateDefinitions:
{
<!--- other UIDL fields -->
"stateDefinitions": {
"isVisible": {
"type": "boolean",
"defaultValue": true
},
"isShareable": {
"type": "boolean",
"defaultValue": false
}
}
<!--- other UIDL fields -->
}
# Component Node
Each component UIDL must contain a single content node
. This node describes what this component looks like when it is displayed. The root node is any element of type UIDLNode
. Check the table from the node types section to understand which nodes can be used as the root node.
Here's an example where the root node is of type element
and renders an image
element:
{
<!---other UIDL fields -->
"node": {
"type": "element",
"content": {
"elementType": "image",
<!--- other UIDL fields -->
}
}
<!--- other UIDL fields -->
}
# Other examples
# Assignments
When assigning a node to a key, we need to declare the node type and pass in content specific to that node type in the content key:
{
"type": "static",
"content": "Hello World!!"
}
A "hello world" message could be written like this:
{
"name": "Message",
"node": {
"type": "element",
"content": {
"elementType": "text",
"children": [
{
"type": "static",
"content": "Hello World!!"
}
]
}
}
}
TIP
To write your UIDLs faster, you can also use something like: "children": ["Hello World!!"]
. This is a valid representation also because the generators automatically assume static content for strings in styles, attributes and children assignments
# Component element with styles and attributes
Components end up having element nodes as leafs most of the time. These elements have styles, attributes, events and dependencies.
Styles and attributes can receive very similar values. They both accept nodes of type static
and dynamic
while the nested-styles
node type is exclusive to style.
{
"name": "ElementWithStylesAndAttributes",
"propDefinitions": {
"title": {
"type": "string",
"defaultValue": "my-value"
}
},
"node": {
"type": "element",
"content": {
"elementType": "div",
"attrs": {
"tab-index": {
"type": "static",
"content": "0"
},
"data-dynamic-attr": {
"type": "dynamic",
"content": {
"referenceType": "prop",
"id": "title"
}
}
},
"style": {
"width": { "type": "static", "content": "100px" },
"@media(max-width: 320px)": {
"type": "nested-style",
"content": {
"width": { "type": "static", "content": "10px" }
}
}
},
"children": [
{
"type": "static",
"content": "Hello"
}
]
}
}
}
# Component element with dependencies
Adding primitive elements like containers and images is not enough to build more complex visual user interfaces. Sometimes we might want to rely on a third party package for a specific component, or we want to define the components ourselves and reuse them in multple place.
In order to do so we have the dependency key option which will allow us to specify what import statements need to appear in the component that uses instances of a given element.
Each element node needs to include it's dependecy declaration for now.
{
"name": "ElementWithDependecies",
"node": {
"type": "element",
"content": {
"elementType": "div",
"children": [
{
"type": "element",
"content": {
"attrs": {
"some-value": {
"type": "static",
"content": "1"
}
},
"elementType": "ReactDatepicker",
"dependency": {
"type": "package",
"path": "react-datepicker",
"version": "1.0.2",
"meta": {
"namedImport": false
}
}
}
},
{
"type": "element",
"content": {
"attrs": {
"authorName": {
"type": "static",
"content": "Emma"
}
},
"elementType": "AuthorCard",
"dependency": {
"type": "local"
}
}
}
]
}
}
}
# Root Component UIDL
TIP
Root Component is a extended version of Component UIDL
. All fields in Component UIDL
are supported
in RootComponent UIDL
.
The additational fields are used to define global styles and dependencies. These can be directly used in project or just some supported dependencies.
# Peer Definitions
When we start using external dependeencies in our project. They might need a additational packages to work with, which wee might not directly use in the project. Let's see a example.
Let us consider we are using components from chakra-ui (opens new window). We define these components in UIDL using
{
"name": "Simple Component",
"node": {
"type": "element",
"content": {
"elementType": "component",
"semanticType": "Button",
"attrs": {
"colorScheme": "blue"
},
"dependency": {
"type": "package",
"path": "@chakra-ui/core",
"version": "0.8.0",
"meta": {
"namedImport": true
}
},
"children": ["Button"]
}
}
}
When we run these through code-generators, the generators will auto-import @chakra-ui/core
and add them to package.json
at the end.
But @chakra-ui/core
needs @emotion/core
, @emotion/styled
and emotion-theming
to work. These are pseudo
dependencies which are not directly used in the project. But we need them for the project to work.
So, we need to define them under root
using peerDeefinitions
<!-- other Project UIDL fields --->
"root": {
"peerDefinitions":{
"@emotion/core":{
"type":"package",
"path":"@emotion/core",
"version":"^10.0.34"
},
"@emotion/styled":{
"type":"package",
"path":"@emotion/styled",
"version":"^10.0.27"
},
"emotion-theming":{
"type":"package",
"path":"emotion-theming",
"version":"^10.0.27"
}
}
}
<!-- other Project UIDL fields --->
These peerDefinitions
are collected and added to package.json
at the end. Typescript interface for Peer Definition.
interface UIDLPeerDependency {
type: "package";
path: string;
version: string;
}
# Import Definitions
These are used to define global imports that are needed for the proejct to work. For example, let's consider we are using
antd (opens new window) design system components in our proejct. But antd
exports the css
for all the components
seperately. The stylesheet need to be added globally for the components to render properly.
{
"type": "element",
"content": {
"semanticType": "Button",
"elementType": "component",
"attrs": {
"type": "primary"
},
"children": ["Button from ANTD"],
"dependency": {
"type": "package",
"path": "antd",
"version": "4.5.4",
"meta": {
"namedImport": true
}
}
}
}
Let's consider we are using Button
as below. But now we need a global import of antd
for it to work. These
global imports need to be defined under root
.
<!-- other Project UIDL fields --->
"root": {
"importDefinitions": {
"antdCSS": {
"type": "package",
"path": "antd/dist/antd.css",
"version": "^4.5.1",
"meta": {
"importJustPath": true
}
}
}
}
<!-- other Project UIDL fields --->
importJustPath
is used to just add the import, but omit them adding to package.json
field. A use case, is
adding dependencies
from CDN. We don't need any additational info of CDN in package.json
.
interface UIDLExternalDependency {
type: "library" | "package";
path: string;
version: string;
meta?: {
namedImport?: boolean;
originalName?: string;
importJustPath?: boolean;
useAsReference?: boolean;
};
}
# Style Set Definitions
These are used to define project style sheet. These are converted into css
or css-modules
or styled-components
depending on the
target style variation selected in project geeneerators.
<!-- other Project UIDL fields --->
"root": {
"styleSetDefinitions": {
"1234": {
"id": "1234",
"name": "secondaryButton",
"type": "reusable-project-style-map",
"content": {
"background": "red",
"width": "auto",
"color": "#fff",
"border": "1px solid #fff"
}
}
}
}
<!-- other Project UIDL fields --->
Style Set Definitions can be extended with conditions for media-queries and selectors like hover
, active
etc.
<!-- other Prject UIDL fields --->
"root": {
"styleSetDefinitions": {
"1234": {
"id": "1234",
"name": "primary-button",
"type": "reusable-project-style-map",
"conditions": [
{ "type": "screen-size",
"meta": {
"maxWidth": 991
},
"content": {
"background": "blue"
}
}
],
"content": {
"background": "green",
"width": "auto",
"color": "#fff",
"border": "1px solid #fff"
}
},
}
}
<!-- other Prject UIDL fields --->
We can use tokens too, while defining styleSetDefinitions
. Let's see the tokens more details in the coming
steps (opens new window).
interface UIDLStyleSetDefinition {
id: string;
name: string;
type: "reusable-project-style-map";
conditions?: UIDLStyleSetConditions[];
content: Record<string, UIDLStaticValue | UIDLStyleSetTokenReference>;
}
type UIDLStyleSetConditions =
| UIDLStyleSetMediaCondition
| UIDLStyleSetStateCondition;
interface UIDLStyleSetMediaCondition {
type: "screen-size";
content: Record<string, UIDLStaticValue | UIDLStyleSetTokenReference>;
meta: {
maxWidth: number;
minWidth?: number;
maxHeight?: number;
minHeight?: number;
};
}
interface UIDLStyleSetStateCondition {
type: "element-state";
meta: {
state: "hover" | "active" | "focus" | "disabled";
};
content: Record<string, UIDLStaticValue | UIDLStyleSetTokenReference>;
}
# Design Language
From v0.15.0 (opens new window) we introduced a new field
designLanguage
in Project UIDL. We plan to bring all the design realted meta data for projects under this attribute.
Right now we can define tokens
which can be used in styles to refer some constant values. Design-language is alo defined
under RootComponentUIDL (opens new window).
# Tokens
Tokens are used to store all the design related constants in a single place. They are converted into css-variables
in plain css flavours and in css-in-js
they converted into constants. Tokens in Project UIDL can be defined under root.
<!-- other fields of ProjectUIDL --->
"root": {
"designLanguage": {
"tokens": {
"blue-500": {
"type": "static",
"content": "#9999ff"
},
"blue-600": {
"type": "static",
"content": "#6b7db3"
},
"red-500": {
"type": "static",
"content": "#ff9999"
},
"red-300": "#b36b6b",
"font-size": 45
}
}
}
<!-- other fields of ProjectUIDL --->
Tokens can be defined using a simple static-node
. Once defined they can be used for styles
, media-queries
, project-style-sheet
.
type UIDLDesignTokens = Record<string, UIDLStaticValue>;
When referring to a token, We need to use UIDLStyleSetTokenReference
.
interface UIDLStyleSetTokenReference {
type: "dynamic";
content: {
referenceType: "token";
id: string;
};
}
Using tokens in UIDL, The id
refers to the token-name or the attr
id of the token. For more details on tokens, pleasee
refer the pull-request on GitHub (opens new window).
"background": {
"type": "dynamic",
"content": {
"referenceType": "token",
"id": "blue-600"
}
}
# Project UIDL
A project UIDL is a collection of component UIDLs with some additional information on top, related to global settings, assets and routing.
interface ProjectUIDL {
name: string;
globals: {
settings: {
title: string;
language: string;
};
meta: Array<Record<string, string>>;
assets: GlobalAsset[];
manifest?: WebManifest;
};
root: ComponentUIDL;
components?: Record<string, ComponentUIDL>;
}
The UIDL structure for a project can have the following fields at the root level:
name
- unique string name identifier of the project.root
- object with the component UIDL that is considered to be the entry point in your project. For more details check Root Nodeglobals
- object with project related information. Inside this object, you can nest objects with settings, manifest, assets, global variables or other meta information related to your project. For more details check Globalscomponents
- object containing other UIDL components. The components should be defined according to the pattern defined below.
# Globals
The globals node contains information specific to your project. The following fields can be configured inside this object:
settings
- object containing global settings like: language and title.
{
<!---other UIDL fields -->
"settings": {
"language": "en",
"title": "teleportHQ"
}
<!---other UIDL fields -->
}
assets
- array with objects containing the type of the asset ( e.g.: style, script, icon, font ) and the path to it. Check the sample below:
{
<!---other UIDL fields -->
"assets": [
{
"type": "script",
"content": "console.log('inline script')",
"meta": {
"target": "body"
}
},
{
"type": "font",
"path": "https://fonts.googleapis.com/css?family=Roboto"
}
]
<!---other UIDL fields -->
}
meta
- array with objects containing any other information that is need by the project to run as desired. Here you can add the information you would normally have inside <meta> HTML tag, info like description, keywords, viewport, etc.
{
<!---other UIDL fields -->
"meta" : [
{ "name": "description", "content": "Free Web tutorials" },
{ "name": "keywords", "content": "security" },
{ "name": "viewport", "content": "width=device-width, initial-scale=1.0" },
{ "property": "og:title", "content": "Free Web tutorials" },
],
<!---other UIDL fields -->
}
The following fields are optional inside the globals object:
manifest
- object contaning the information for the webapp manifest file
{
<!---other UIDL fields -->
"manifest": {
"icons": [
{
"src": "/playground_assets/icons-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/playground_assets/icons-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"theme_color": "#822CEC",
"background_color": "#822CEC"
}
<!---other UIDL fields -->
}
# Routing
WARNING
Routing is still an experimental feature and might be subjected to change in the future
In the root field, you configure the entry point of the project. At the moment, this is limited to defining the top level routing mechanism, but in the future this will allow you to describe different layouts for the entire application.
The value of the root
is a Component UIDL.
Below is one sample example of how the root node can look like.
{
<!---other UIDL fields -->
"root": {
"name": "App",
"stateDefinitions": {
"route": {
"type": "string",
"defaultValue": "index",
"values": [
{
"value": "index",
"meta": {
"path": "/",
"componentName": "Home"
}
},
{
"value": "about",
"meta": {
"path": "/about",
"componentName": "About"
}
},
{
"value": "contact-us",
"meta": {
"path": "/here-we-are",
"componentName": "Us"
}
}
]
}
}
<!---other UIDL fields -->
}
Navigation from one page (or state) to another in a application depends on the framework running the app. Modern frameworks implement client side routing via their own libraries.
Since each framework implements its own tricks for navigation, you can use an abstract element
called navlink
. This navlink
is resolved based on the specific project flavor:
- for
next.js
, you would get a <Link> with a dependency tonext/link
- for
vue
, you would get a<router-link>
- and so on ...
{
"type": "element",
"content": {
"elementType": "navlink",
"attrs": {
"transitionTo": {
"type": "static",
"content": "about"
}
},
"children": [
{
"type": "static",
"content": "About Page"
}
]
}
}
The value set on transitionTo
is a state
key which is specified in the values
field in the route
state in the project UIDL. The generators will translate that state key to the url
in case of web based project generators.