GrowSitedevelopers

GS Loom UI components

Shared components, variants and theme styling.

GS Loom UI: one behavior, theme-owned appearance

GS Loom UI v1 is the shared component layer for storefront themes. It is separate from the admin/customer panel library. A theme uses ordinary HTML with data-gs-ui; GS Loom adds common CSS and interactive behavior. The theme supplies colors, radii and any custom variants. Existing HTML, animations, schemas and plugin permissions continue to work.

This page explains components through source code and configuration examples. Theme packages also contain snippets/ui-demo.html for optional testing on a private development page.

Contract and variants

html
<button type="button" data-gs-ui="button" data-variant="primary" data-size="md">
  Get started
</button>
<a href="/contact" data-gs-ui="button" data-variant="outline">Contact</a>

Use a real button for an action and a real link for navigation. Variants change appearance, not semantics. data-size changes size independently of data-variant. Existing theme classes can coexist with these attributes. The contract is additive: migration does not require replacing every existing element or removing animation hooks.

Component HTML contract Built-in variants
Button button or a with data-gs-ui="button" primary (default), secondary, outline, ghost, link, danger; sizes sm, md (default), lg
Card article or div with data-gs-ui="card" surface (default), outline, elevated, ghost
Badge span with data-gs-ui="badge" primary (default), secondary, outline
Field wrapper with data-gs-ui="field" shared 8 px label/control spacing
Input / Textarea input data-gs-ui="input" / textarea data-gs-ui="textarea" outline (default), filled, underline
Select div data-gs-ui="select" containing a single native select outline (default), filled
Dropdown details data-gs-ui="dropdown", summary, content wrapper secondary (default), outline, primary
Tabs div data-gs-ui="tabs", tab list, buttons and panels line (default), pills; horizontal or vertical
Accordion details data-gs-ui="accordion" with summary surface (default), outline, ghost
Modal dialog data-gs-ui="modal" default, surface; sizes md (default), lg

A custom variant such as brand is allowed. It does not gain behavior automatically: define its appearance in the theme CSS. A variant meaningful for a button is not automatically meaningful for an input or modal.

Theme tokens and custom variants

Put tokens in assets/ui.css. GS Loom loads shared styles before theme assets. In a Loom theme the following rule is scoped to that theme; document themes receive the same CSS in their isolated document.

css
:scope {
  --gs-ui-accent: #e59d02;
  --gs-ui-on-accent: #161006;
  --gs-ui-text: #f8f8f8;
  --gs-ui-background: #040000;
  --gs-ui-surface: #191715;
  --gs-ui-border: #ffffff29;
  --gs-ui-focus: #e59d02;
  --gs-ui-radius: 20px;
  --gs-ui-button-radius: 50px;
  --gs-ui-shadow: 0 16px 40px #0003;
}
[data-gs-ui="button"][data-variant="brand"] {
  background: #5347ce;
  color: #ffffff;
  border-color: #5347ce;
}

You may also override --gs-ui-danger and --gs-ui-on-danger. Bind tokens to your theme settings when colors are editable. Choose foreground and background together and test contrast. Skeleton uses yellow on dark surfaces, Paylio dark buttons on a light surface, and Victorie orange buttons with light/dark surfaces. Imported themes inherit the platform's base components; their token file is part of the exported theme package.

Editable button variants

Declare a select setting in the block schema, then bind it to data-variant:

json
{
  "type": "select",
  "id": "button_variant",
  "label": "Button variant",
  "default": "primary",
  "options": [
    {"value":"primary","label":"Primary"},
    {"value":"secondary","label":"Secondary"},
    {"value":"outline","label":"Outline"}
  ]
}
html
<a data-gs-ui="button" data-variant="[[ block.settings.button_variant ]]"
   href="[[ safe_url(block.settings.url) ]]"
   data-text-field="[[ block.settings_path ]].label"
   gs-text="block.settings.label"></a>

Skeleton's existing Card block exposes this setting. The same pattern works for card or other applicable variants. A schema select configures the editor; it is different from a storefront Select component. The shared UI does not automatically add a block to the builder: define a block schema, preset and parent targeting as usual.

Inputs and Select

html
<div data-gs-ui="field">
  <label for="contact-topic">Topic</label>
  <div data-gs-ui="select" data-variant="filled">
    <select id="contact-topic" name="topic" required>
      <option value="">Choose a topic</option>
      <option value="sales">Sales</option>
      <option value="support">Support</option>
      <option value="other" disabled>Unavailable</option>
    </select>
  </div>
</div>

After initialization the platform presents a custom combobox/listbox. The native select remains the form value and validation source. Arrow keys move between enabled options, Home/End reach the first/last option, typing finds a label, Enter/Space select, Escape cancels and Tab closes the list. Clicking outside closes it. change and input events and form reset retain standard behavior. Without JavaScript the native select remains usable. Multiple selection is not enhanced in v1.

Keep a real label associated with the native select's id; the runtime transfers its accessible name to the trigger. Use unique IDs per instance, for example derived from block.id, rather than copying example IDs into repeated blocks. Put an Input or Textarea in the same Field wrapper, with a matching label. Use disabled, required and aria-invalid as appropriate. UI primitives provide no submission endpoint: use the existing Forms plugin for sending data and server validation. The library does not replace plugin-owned field markup or bypass access checks.

html
<details data-gs-ui="dropdown" data-variant="outline">
  <summary>Explore</summary>
  <nav data-gs-dropdown-content aria-label="Explore">
    <a href="/about">About</a>
    <a href="/contact">Contact</a>
  </nav>
</details>

Enter/Space opens the native disclosure. Tab moves through links. Escape closes and returns focus to the summary; leaving the control or clicking outside also closes it. This is a disclosure with links, not an ARIA menu. Do not add menu roles without the corresponding keyboard behavior.

<gs-languages variant="dropdown"></gs-languages> uses the same dropdown behavior and retains the language plugin's navigation/editor contract. Each Languages-capable theme must supply its own switcher appearance. Skeleton's assets/languages.css extends its shared UI tokens. The plain link variant remains available. See Languages.

Tabs and Accordion

html
<div data-gs-ui="tabs" data-variant="pills" data-value="overview">
  <div data-gs-tab-list aria-label="Product information">
    <button data-gs-tab="overview">Overview</button>
    <button data-gs-tab="details">Details</button>
  </div>
  <section data-gs-panel="overview">Overview content</section>
  <section data-gs-panel="details">Detailed content</section>
</div>
<details data-gs-ui="accordion" data-variant="outline">
  <summary>How does this work?</summary>
  <p>Your answer.</p>
</details>

Tab keys match panel keys inside their component. The runtime generates IDs, roles and relationships, hides inactive panels and manages roving keyboard focus. Arrow keys, Home and End select enabled tabs automatically; data-orientation="vertical" uses up/down arrows. Tab and panel definitions should remain stable during a mounted instance; remount when replacing the whole set. Before JavaScript initializes, all panel content remains readable.

Accordion uses native details behavior. Add open for an initially expanded item. Use the same name on related details when you want an exclusive group; choose a unique group name per block instance.

html
<button type="button" data-gs-ui="button" data-gs-open="shipping-dialog">Shipping</button>
<dialog data-gs-ui="modal" data-size="lg" id="shipping-dialog" aria-labelledby="shipping-title">
  <h2 id="shipping-title">Shipping information</h2>
  <p>Content shown on demand.</p>
  <button type="button" data-gs-ui="button" data-variant="secondary" data-gs-close autofocus>Close</button>
</dialog>

The runtime opens native dialog with showModal(), providing modal focus containment, Escape and focus return. The close control and a click outside the dialog rectangle close it. Give it an accessible title and an obvious close button. Targets are resolved within the current theme wrapper; repeated blocks need unique target IDs. A modal is not a confirmation or permission system and does not submit anything by itself.

Focus, active and disabled states

Focus is visible without changing the component's shape. :active describes a pressed control; persistent selection uses aria-selected, aria-current, or details[open]. Native disabled buttons do not activate. For disabled links use aria-disabled="true" and tabindex="-1"; the shared runtime prevents activation. Do not use a disabled-looking class as access control. See focus rules for composite search fields and dropdowns.

Migration and verification

The three bundled themes now include UI tokens and a reusable UI demo. Existing Skeleton/Paylio button and card markup and Victorie CTA buttons opt into the shared contract while retaining their original classes and animation hooks. Specialized navigation, sliders and video players keep their existing behavior. These are not silently replaced with generic controls.

Test variants and themes, keyboard navigation, labels, validation, disabled options, form reset, modal Escape/focus return, two instances on one page, mobile overflow and reduced motion. Keep CSS and UI code changes in the theme/runtime, not in saved customer content. Rebuild with npm run loom:build; the developer portal download is rebuilt with npm run build:developers. Do not copy platform runtime files into every theme: GS Loom supplies them in rendering and preview.

Equal height and grow

Use data-gs-equal-height on a flex row or grid. Each direct child becomes a flex wrapper; put data-gs-grow on the card or button inside it. Items stretch to the tallest content in their row, including after translation or live editing. The theme still defines columns, gaps and responsive breakpoints. No fixed heights or JavaScript measurements are needed.

html
<div data-gs-equal-height class="feature-grid">
  <div>
    <article data-gs-ui="card" data-gs-grow data-gs-stack>
      <h3>Feature</h3>
      <p>Short or long translated content.</p>
      <div data-gs-footer>
        <a data-gs-ui="button" href="/details">Details</a>
      </div>
    </article>
  </div>
  <!-- Repeat the wrapper for each card. -->
</div>
css
.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 24px;
}

data-gs-stack creates a vertical flex layout; data-gs-footer pushes actions to the bottom. Keep normal spacing above actions (for example a gap or footer padding). data-gs-grow also works independently inside flex layouts. On mobile, separate rows retain natural heights. For equal heights across ALL grid rows, explicitly add grid-auto-rows: 1fr to the grid. These attributes work with any component variant and do not replace its colors, borders or animation.

Header and menu editing

GS Loom uses data-gs-header="header" for headers, data-gs-logo for logos and data-gs-menu="header" or "footer" for navigation. <gs-menu location="header"> emits this marker automatically and reads the site menu. Preserve semantic header, nav and links. Themes own appearance, breakpoints and dropdowns.

For a schema-owned header, add data-gs-header-group="header". Clicking the header, logo or menu item opens that group in the builder; edits persist in loom.groups.header (the legacy data property name used by GS Loom). Skeleton uses this variant to retain its nested navigation and translations. Without this attribute, logos open the shared logo editor and menus the site navigation manager. Do not mix both data sources for the same items. Public links behave normally.

GS Loom separates the menu data from its appearance. The builder stores a tree of items; <gs-menu> renders that tree, and the theme CSS controls its appearance. The examples below are source code, not interactive previews.

1. Place the menu in the header

html
<gs-menu location="header" label="Main navigation"></gs-menu>

Put this markup in the theme header section or snippet. location="header" selects the site menu assigned to that location; label is its accessible name. Do not paste the JSON below into this HTML: it illustrates items stored in the site menu. In the builder, use Navigation → Header, expand an item and choose Add child item.

Editing the header menu enables use_site_menu. Paylio uses the shared header; Skeleton and Victorie Vending keep their original demo navigation until this setting is enabled. Use one data source for each menu, rather than maintaining both hardcoded links and site menu items.

2. Dropdown with children and grandchildren

json
{
  "id": "services",
  "label": "Services",
  "href": "/services",
  "menu_layout": "dropdown",
  "children": [
    {
      "id": "websites",
      "label": "Websites",
      "href": "/websites",
      "icon": "globe",
      "children": [
        {
          "id": "design",
          "label": "Design",
          "href": "/design",
          "icon": "star"
        },
        {
          "id": "development",
          "label": "Development",
          "href": "/development",
          "icon": "code"
        }
      ]
    }
  ]
}

Services → Websites → Design / Development creates three levels. Each object in children follows the same contract, so a child can contain its own children. Without children, an item renders as a link. Omitting menu_layout uses dropdown. This object belongs in the header menu’s items array.

Field Meaning
id Stable item identifier; keep it when renaming or reordering.
label Visible link or submenu trigger text.
href Destination URL. Use # for a parent that only opens its submenu.
children Array of child items; omit it for a leaf link.
menu_layout dropdown for a list, mega for columns of immediate children.
icon Library icon key or image:<URL>; omit for no icon.

3. Megamenu with icons

json
{
  "id": "explore",
  "label": "Explore",
  "href": "#",
  "menu_layout": "mega",
  "children": [
    {
      "id": "products",
      "label": "Products",
      "href": "/products",
      "icon": "star",
      "children": [
        {
          "id": "skeleton",
          "label": "Skeleton",
          "href": "/products/skeleton"
        },
        {
          "id": "paylio",
          "label": "Paylio",
          "href": "/products/paylio"
        }
      ]
    },
    {
      "id": "support",
      "label": "Support",
      "href": "/support",
      "icon": "image:/media/support.svg",
      "children": [
        {
          "id": "contact",
          "label": "Contact",
          "href": "/contact"
        },
        {
          "id": "docs",
          "label": "Documentation",
          "href": "/docs"
        }
      ]
    }
  ]
}

Set menu_layout: "mega" on the parent that opens the wide panel. Here Products and Support form two columns; their children contain the links below each heading. star selects a library icon. image:/media/support.svg illustrates an uploaded image: replace it with the real URL returned by the file manager. The property accepts an image URL, not raw SVG markup. You can change either column’s children without changing the renderer.

4. Match the theme appearance

css
[data-gs-menu="header"] {
  --gs-menu-background: #17201c;
  --gs-menu-color: #ffffff;
  --gs-menu-border: #ffffff33;
  --gs-menu-radius: 16px;
  --gs-menu-gap: 24px;
}

Add these rules to the theme stylesheet. The selector limits the tokens to the header menu. Background, text, border, corner radius and spacing can differ between themes while retaining shared behavior. Keep focus indicators visible and ensure panels fit the viewport.

5. Behavior, translations and limits

Click or Enter/Space opens a submenu; Escape closes the current level and returns focus to its trigger. If a parent has a real URL, the renderer adds that link at the start of its panel, so opening the panel does not navigate away. On small screens nested panels expand vertically. A megamenu is not a separate page or plugin.

With Languages enabled, edit labels per language in the builder. Keep item IDs stable so translations remain associated with the correct items; icons, URLs and layout are language-independent. Limits are 5 levels including the root, 30 siblings per branch and 100 items per menu. Check long translations, keyboard operation and mobile layouts. See Menus for the site navigation data contract and Languages for localization.

Visual menu editing in the builder

Click the Skeleton header to open the menu tree. Only existing items are listed; expand a parent to see its children. Select an item and click its name to edit it directly. Its link, icon and submenu layout are shown only for that selection. Add, remove and reorder items without editing schema fields. The Appearance tab controls text/background/active colors, font, font size and spacing. Apply writes a draft and preserves localized labels; publication remains a separate action.

Download Markdown copy
Results · 22
GS Loom UI components

Shared components, variants and theme styling.

Font catalog

Available fonts, previews and font declarations in GS Loom.

Menus and navigation

Create menus, connect links and style navigation.

Blog and comments

Blog and comments

SEO and plugin contracts

SEO and plugin contracts

Build your first theme

A complete package, installation and your first edit.

GS Loom Playground

Test themes locally without an API, database or upload.

Architecture and files

How author files become a customer website.

Schema and settings

Control types, default values and stable identifiers.

Blocks, targeting and order

Definitions, instances, nesting and static blocks.

Preview editing

Connect text, cards, images and icons to the builder.

Text and headings

From schema to text editing, typography and persistence.

Buttons and links

Labels, destinations, colors, dimensions and accessible states.

Inputs and form fields

Field types, labels, options, limits and validation.

Forms step by step

Instances, site binding, submission and error handling.

Cards, icons, images and badges

Container appearance, icon sizes and media editing.

Dynamic sources

Site data, page context and fallback values.

Layouts and shared settings

Section groups, global configuration and page templates.

Languages and translations

Starter languages, customer-added languages and AI translations.

Snippets, CSS and assets

Reusable markup, responsive styling and safe URLs.

Testing, import and updates

Validate a package and release it while preserving customer data.

Capabilities and limits

The supported GS Loom contract and working with AI.