Before we proceed with explaining the structure of the skeleton.json file, let's quickly remind ourselves of the basic website structure. Below is an example HTML DOM body element from Intro component with sign-up form:
<body> <main> <section> <h1>Learn to code by watching others</h1> <p>See how experienced developers solve problems in real-time. Watching scripted tutorials is great, but understanding how developers think is invaluable.</p> </section> <div> <div>Try it free 7 days <span> then $20/mo. thereafter</span></div> <form method="GET"> <ul> <li><label><input id="first-name" name="first-name" aria-label="First Name" placeholder="First Name" autocomplete="given-name" required="" pattern="^[a-zA-Z\s]+$"></label></li> <li><label><input id="last-name" name="last-name" aria-label="Last Name" placeholder="Last Name" autocomplete="family-name" required="" pattern="^[a-zA-Z\s]+$"></label></li> <li><label><input id="email" name="email" aria-label="Email Address" placeholder="Email Address" autocomplete="email" required="" pattern="^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$"></label></li> <li><label><input id="password" name="password" aria-label="Password" placeholder="Password" required=""></label></li> </ul><button> <div><span>CLAIM YOUR FREE TRIAL</span></div> </button> <p>By clicking the button, you are agreeing to our <span>Terms and Services</span></p> </form> </div> </main> </body>
This construction serves as a building block for defining the structure of a website. To enhance the visual aspect of the elements, an accompanying stylesheet is essential. While it may be tempting to apply styles directly to elements using attributes, this approach can hinder proper caching and limit the styles to specific elements, missing out on broader application. Over the years, CSS has evolved with advanced features, and my personal favorite is Tailwind CSS. Its utility-first classes enable you to build custom designs directly within your markup.
Here is the skeleton.json file for the same design. It defines the structure of a route in a cwrap project:
{ "element": "body", "style": "background-color: #FF7979; background-image: url('./static/images/bg-intro-desktop.png'); background-repeat: repeat; box-sizing: border-box; display: flex; flex-direction: column; font-family: 'Poppins', serif; justify-content: center; margin: 0; min-height: 100dvh; padding: 24px;", "mediaQueries": [ { "query": "max-width: 1024px", "style": "background-image: url('./static/images/bg-intro-mobile.png'); justify-content: center; padding: 88px 24px 68px 24px;" } ], "children": [ { "element": "main", "style": "display: flex; gap: 32px; justify-content: space-between; margin-inline: auto; max-width: 1110px; min-height: 558px; width: 100%;", "mediaQueries": [ { "query": "max-width: 1024px", "style": "flex-direction: column; gap: 64px; max-width: fit-content;" } ], "children": [ { "element": "section", "style": "align-items: start; display: flex; flex-direction: column; gap: 11px; justify-content: center; max-width: 525px; text-align: start; width: 100%;", "mediaQueries": [ { "query": "max-width: 1024px", "style": "align-items: center; text-align: center;" } ], "children": [ { "element": "h1", "text": "cwrapGlobal[section.title]", "style": "color: white; font-size: 50px; font-weight: bold; letter-spacing: -0.52px; line-height: 55px; margin: 0;", "mediaQueries": [ { "query": "max-width: 640px", "style": "font-size: 28px; letter-spacing: -0.29px; line-height: 36px;" } ] }, { "element": "p", "text": "cwrapGlobal[section.description]", "style": "color: white; font-size: 16px; font-weight: 500; line-height: 26px;" } ] }, { "element": "div", "style": "display: flex; flex-direction: column; gap: 24px; max-width: 540px; width: 100%;", "children": [ { "element": "div", "text": "Try it free 7 days ", "style": "align-items: center; background-color: #5E54A4; border-radius: 10px; box-shadow: 0 8px 1px rgba(0, 0, 0, 0.25); color: white; display: flex; font-size: 15px; font-weight: bold; justify-content: center; letter-spacing: 0.27px; line-height: 26px; min-height: 60px;", "mediaQueries": [ { "query": "max-width: 640px", "style": "align-content: center; display: block; min-height: 88px; text-align: center; white-space: pre-wrap;" } ], "children": [ { "element": "span", "text": " then\n$20/mo. thereafter", "style": "font-weight: 400;" } ] }, { "element": "form", "attributes": { "method": "GET" }, "style": "align-items: center; background-color: white; border-radius: 10px; box-shadow: 0 8px 1px rgba(0, 0, 0, 0.25); box-sizing: border-box; display: flex; flex-direction: column; min-height: 474px; padding: 40px; width: 100%;", "mediaQueries": [ { "query": "max-width: 640px", "style": "padding: 24px;" } ], "children": [ { "element": "ul", "style": "display: flex; flex-direction: column; gap: 20px; list-style: none; margin: 0; padding: 0; width: 100%;", "mediaQueries": [ { "query": "max-width: 640px", "style": "gap: 16px;" } ], "blueprint": { "count": "4", "element": "li", "style": "display: flex; width: 100%;", "children": [ { "element": "label", "style": "display: flex; position: relative; width: 100%;", "children": [ { "element": "input", "style": "border: 1px solid #DEDEDE; border-radius: 5px; color: #3D3B48; display: flex; font-size: 14px; font-weight: 600; letter-spacing: 0.25px; line-height: 26px; margin: 0; min-height: 56px; outline: none; padding: 0; padding-inline: 32px; width: 100%;", "mediaQueries": [ { "query": "max-width: 1024px", "style": "padding-inline: 24px;" }, { "query": "max-width: 640px", "style": "padding-inline: 19.5px;" } ], "extend": [ { "extension": ":focus", "style": "border-color: #5E54A4;" }, { "extension": ":user-invalid", "style": "background: url('static/images/icon-error.svg') no-repeat right 27px top 16px; border: 2px solid #FF7979; margin-bottom: 20px; padding-right: 60px; position: relative; z-index: 2;", "mediaQueries": [ { "query": "max-width: 1024px", "style": "padding-right: 44px;" }, { "query": "max-width: 640px", "style": "padding-inline: 39.5px;" } ] } ], "attributes": { "id": "cwrapArray[first-name,last-name,email,password]", "name": "cwrapArray[first-name,last-name,email,password]", "aria-label": "cwrapArray[First Name,Last Name,Email Address,Password]", "placeholder": "cwrapArray[First Name,Last Name,Email Address,Password]", "autocomplete": "cwrapGlobal[cwrapArray[inputs.0.autocomplete,inputs.1.autocomplete,inputs.2.autocomplete,cwrapOmit]]", "required": "", "pattern": "cwrapGlobal[cwrapArray[inputs.0.pattern,inputs.1.pattern,inputs.2.pattern,crapOmit]]" } } ] } ] } }, { "element": "button", "style": "background-color: #38B57E; border: 1px solid transparent; border-radius: 5px; cursor: pointer; letter-spacing: 1px; margin-top: 20px; min-height: 56px; outline: none; padding: 0; position: relative; transition: background-color 300ms; width: 100%;", "extend": [ { "extension": ":focus", "style": "border: 1px solid #5E54A4;" }, { "extension": "> div:nth-of-type(1)", "style": "transition: background-color 300ms;" }, { "extension": ":hover > div:nth-of-type(1)", "style": "background-color: #77E2B3;" }, { "extension": ":hover", "style": "background-color: #6FCBA2;" } ], "children": [ { "element": "div", "style": "align-items: center; background-color: #38CC8B; border-radius: 5px; color: white; display: flex; font-size: 15px; font-weight: 600; justify-content: center; line-height: 26px; min-height: 52px; position: absolute; top: 0; width: 100%;", "children": [ { "element": "span", "style": "margin-top: 4px;", "text": "CLAIM YOUR FREE TRIAL" } ] } ] }, { "element": "p", "text": "By clicking the button, you are agreeing to\nour ", "style": "color: #BAB7D4; font-size: 11px; font-weight: 500; line-height: 26px; text-align: center;", "mediaQueries": [ { "query": "max-width: 640px", "style": "line-height: 21px; white-space: pre-wrap;" } ], "children": [ { "element": "span", "text": "Terms and Services", "style": "color: #FF7979; font-weight: bold;" } ] } ] } ] } ] } ] }
The skeleton.json file is a JSON object that defines the structure of a route in a cwrap project. It consists, in most cases, of these common properties: - element: the HTML element to be rendered, - text: the text content to be displayed within the element, - style: the CSS styles to be applied to the element, - extend: an array of pseudo-classes and corresponding styles, - mediaQueries: an array of media queries and corresponding styles, - attributes: an object containing the attributes to be applied to the element, - blueprint: an object containing the blueprint for generating multiple child elements, - children: an array of child elements. The skeleton.json file is used by the cwrap compiler to generate the HTML and CSS code for a route. It provides a structured way to define the layout and styling of a website, making it easier to maintain and update the design.
Here we have HTML DOM head element of the same project:
<head> <title>New Candy Wrapper project</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" type="text/css"> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta charset="UTF-8"> <meta name="description" content="New Candy Wrapper project."> <meta name="keywords" content=""> <link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="globals.css"> </head>
And here is the skeleton.json file for the head element:
{ "head": { "title": "New Candy Wrapper project", "link": [ { "rel": "stylesheet", "href": "https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap", "type": "text/css" }, { "rel": "shortcut icon", "href": "favicon.ico", "type": "image/x-icon" }, { "rel": "stylesheet", "href": "styles.css" }, { "rel": "stylesheet", "href": "globals.css" } ], "meta": [ { "name": "viewport", "content": "width=device-width, initial-scale=1.0" }, { "charset": "UTF-8" }, { "name": "description", "content": "New Candy Wrapper project." }, { "name": "keywords", "content": "" } ] } }
I think it is pretty much self-explanatory.
The skeleton.json file, due to its unbounded JSON nature, allows for a more flexible pattern than the regular DOM. This flexibility is beneficial for defining complex structures and styles in a more manageable and scalable way. Unlike the standard DOM, which has its limitations, the skeleton.json format can accommodate a wide range of design and layout requirements, making it an excellent choice for projects that demand a high degree of customization and adaptability.
{ "classroom": [ { "name": "placeholder", "type": "pseudo::", "style": "color: rgba(61, 59, 72, 0.75);" }, { "name": "user-invalid::placeholder", "type": "pseudo:", "style": "color: rgba(223, 86, 86, 0.5);" }, { "name": "label:has(*:user-invalid:placeholder-shown)::before", "type": "element", "style": "bottom: -4px; color: #FF7979; font-size: 11px; font-style: italic; font-weight: 500; position: absolute; right: 0;" }, { "name": "li:nth-of-type(1) > label:has(*:user-invalid:placeholder-shown)::before", "type": "element", "style": "content: 'First Name cannot be empty';" }, { "name": "li:nth-of-type(2) > label:has(*:user-invalid:placeholder-shown)::before", "type": "element", "style": "content: 'Last Name cannot be empty';" }, { "name": "li:nth-of-type(3) > label:has(*:user-invalid:placeholder-shown)::before", "type": "element", "style": "content: 'Password cannot be empty';" }, { "name": "li:nth-of-type(4) > label:has(*:user-invalid:placeholder-shown)::before", "type": "element", "style": "content: 'Password cannot be empty';" }, { "name": "label:has(*:user-invalid)::before", "type": "element", "style": "bottom: -4px; color: #FF7979; font-size: 11px; font-style: italic; font-weight: 500; position: absolute; right: 0;" }, { "name": "li:nth-of-type(1) > label:has(*:user-invalid)::before", "type": "element", "style": "content: 'Looks like this is not a first name';" }, { "name": "li:nth-of-type(2) > label:has(*:user-invalid)::before", "type": "element", "style": "content: 'Looks like this is not a last name';" }, { "name": "li:nth-of-type(3) > label:has(*:user-invalid)::before", "type": "element", "style": "content: 'Looks like this is not an email';" } ] }
Above is an example of a skeleton.json file that defines custom styles for form validation messages. The file contains a list of CSS selectors and corresponding styles that are applied to the elements based on their state. This approach allows for a more granular control over the appearance of the elements and provides a way to define complex styles that are not easily achievable with standard CSS.