Documentation index

Template documentation

The EchoSistema starter guide: how to run it, where everything lives and how to use the system's tokens and components.

Introduction

The Nuxt starter for the group's platform sites: the whole Liquid Glass parameterized by four brand tokens.

Brand tokens

Four hex values and one gradient derive all the CSS through color-mix; swapping the palette means editing one block.

Light and dark themes

Automatic parity: every themeable value has a dark twin, and the right theme paints before first paint.

Three languages

Canonical Spanish at the root, pt-BR and English prefixed, a single catalog and locales in lockstep.

Native components

The browser's dialog, details and selects dressed by the system: accessibility for free.

Quick start

pnpm installs, runs and validates. The same scripts as package.json, no hidden steps.

CommandRole
pnpm devDevelopment server with hot reload.
pnpm buildProduction build (SSR).
pnpm generatePrerendered static site.
pnpm previewServes the production build locally.
pnpm lintESLint across the whole project.
pnpm typecheckvue-tsc over the app and the configuration.

Structure

Standard Nuxt 4 layout: app/ is the site, i18n/ the texts, shared/ what server and client share.

app/
  assets/css/main.css
  components/
  composables/
  data/
  layouts/
  pages/
  plugins/
  utils/
i18n/locales/
shared/
nuxt.config.ts

main.css is the single style entry point: .vue files load no style blocks. The language catalog lives in shared/languages.ts and the navigation in app/data/nav.ts.

Brand tokens

The MARCA block at the top of main.css is the only place with brand hex values.

Backgrounds, text, glass, buttons and the code panel all derive from these four tokens through color-mix, with automatic light/dark parity. To rebrand a derived site, swap this block and nothing else; the agro and consumer palettes sit commented in the file as examples.

:root {
  --brand-1: #426ab2;
  --brand-2: #40c1c3;
  --brand-deep: #040b2d;
  --brand-link: #3d91cf;
}

Theme and languages

The theme flips on html[data-theme]; the language owns the URL.

data-theme

A pre-paint script in the head reads the stored preference (or the system's) and sets data-theme before first paint: no flash of the wrong theme. Runtime switching is the same attribute.

prefix_except_default

Canonical Spanish lives unprefixed; /pt-BR and /en prefix the other routes. Adding a language is one entry in the shared/languages.ts catalog plus the matching locale file.

Content width

One ruler for the masthead, the pages and the footer - and three values for it.

Everything that uses `.es-shell` follows the same ruler, which is why the reader’s preference moves the bar and the page together. Before the class existed each file wrote its own, and the home page was born narrower than the bar above it. Docs and the panel are ALWAYS full width and say so with `.es-shell--full`.

ValueRuler
compact90rem · the reading column
extended110rem · what the template ships with
full- · no ceiling, to the edges
# .env - a específica vence a genérica
NUXT_TEMPLATE_THEME_WIDTH=extended
NUXT_THEME_WIDTH=extended

The value is baked at build time: it travels in the script that paints the width before the first frame, and a width arriving after hydration is a layout jump in the reader’s face. After changing .env, rebuild. The floating gear switches per reader, and that choice lives in their browser.

Typography

Semantic content is dressed by its container: .es-prose styles the elements inside it.

Headings, paragraphs, quotations, code and the other text elements pick up the system's style by entering an .es-prose, with no per-element utilities. The loaded families are Manrope, Montserrat and JetBrains Mono.

<div class="es-prose">
  <h2>…</h2>
  <p>…</p>
  <blockquote>…</blockquote>
</div>
See the typography reference

Inventory

The 41 components in app/components/echo, by role. Each dresses a native element when one exists: that is where keyboard, focus and announcement come from, with no reimplementation.

Actions and navigation

Button · Chip · Badge · Pill · Dropdown · Pagination · Breadcrumb · Tabs

Forms

Field · Input · Textarea · Select · SearchSelect · TagsInput · Checkbox · Radio · Switch · Slider · Rating · FileButton · Dropzone · DatePicker · PhoneInput

Feedback and waiting

Alert · Modal · Drawer · Toaster · Block · Skeleton · Spinner · Progress · Ring

Content

Card · List · ListItem · DataTable · Avatar · Gallery · Tree · Timeline · Collapse · Divider · Stat · Player

Media and codes

Chart · Map · Carousel · Editor · ImageCropper · ImageViewer · QrCode · Barcode

Calendar

Calendar · CalendarMini · app/utils/calendar.ts

Account modals

LoginForm · RegisterForm · ForgotPasswordForm · UpdatePasswordForm · PageShell · LoginModal · RegisterModal · ForgotPasswordModal · UpdatePasswordModal · LockModal · LoginPrompt

Chrome (bars, footers, rails)

MastheadEcommerce · MastheadRealEstate · MastheadLearn · MastheadHelpDesk · MastheadLanding · FooterEcommerce · FooterRealEstate · FooterLearn · FooterHelpDesk · FooterLanding · SidebarPanel · SidebarDocs · SidebarFilters · ElementThemeSwitch · ElementLanguageSwitch · ElementCurrencySwitch · ElementSwitchDropdown

Intents and variants

Two axes never combined by hand: .es-i--* provides the color, the variant consumes it.

Ten intents define local tokens (--i, --i-on, --i-grad and derivatives) consumed by the four label families and the modal. The variants (filled, gradient, outline, glass, ghost) are each written once.

primarysuccessdangerwarningcautiondark
<echo-button variant="filled" intent="danger">…</echo-button>

<echo-chip variant="soft" intent="info">…</echo-chip>

<echo-badge variant="filled" intent="success">…</echo-badge>

<echo-pill variant="outline" intent="caution">…</echo-pill>

Alerts

Eight tones over the same palette, with the accent wedge on the edge.

Four semantic status tones (info, success, warn, danger) and four named tonal ones (caution, steel-blue, navy-blue, dark). Text stays in the theme's grays; the accent goes into the icon, the title and the wedge.

<echo-alert tone="success" title="…">
  …
</echo-alert>

Modals

The native dialog: trapped focus, Esc, inert and blurred background.

An opaque theme surface, an intent wedge like the alerts, a background blurred into illegibility while open and locked scrolling. Clicking outside does not close; Esc, the header close and the footer actions do. Width follows the sm, md, lg, xl and xxl scale.

<echo-modal ref="modal" title-id="…"
            intent="warning" size="lg">
  <template #title>…</template>
  …
  <template #footer>…</template>
</echo-modal>

modal.value?.open()

How to use each one

The shortest line that puts each component on screen, ready to copy.

Every component carries the same snippet in its own file, in a "Copy and paste" block right above the code - that is where the full prop list lives. What is here are the minimum usages, to find what fits without opening seven files.

Actions and navigation

<echo-button variant="filled" intent="primary">Save</echo-button>
<echo-chip>tag</echo-chip> · <echo-badge intent="success">ok</echo-badge> · <echo-pill>4</echo-pill>
<echo-dropdown label="Account"><template #trigger>…</template>…</echo-dropdown>
<echo-tabs v-model="tab" :tabs="TABS" />
<echo-pagination v-model="page" :pages="12" />
<echo-breadcrumb :items="TRAIL" />

Forms

<echo-field v-slot="{ id, fieldName }" label="E-mail" name="email"><echo-input :id="id" v-model="email" :name="fieldName" /></echo-field>
<echo-input v-model="text" type="email" size="sm" />
<echo-textarea v-model="note" rows="3" />
<echo-select v-model="topic"><option value="a">A</option></echo-select>
<echo-search-select v-model="country" list-id="countries" :options="names" />
<echo-date-picker v-model="birthday" mode="date" :start-year="1990" />
<echo-phone-input v-model="phone" country="br" @validity="ok = $event" />
<echo-checkbox v-model="agreed">I agree</echo-checkbox>
<echo-radio v-model="skin" name="skin" value="bordered" />
<echo-switch v-model="on" />
<echo-slider v-model="zoom" :min="1" :max="4" :step="0.01" />
<echo-rating v-model="stars" />
<echo-tags-input v-model="tags" />
<echo-file-button accept="image/*" @files="onFiles">Choose</echo-file-button>
<echo-dropzone accept="image/*" @files="onFiles">…</echo-dropzone>

Feedback and waiting

<echo-alert tone="danger" title="That failed">…</echo-alert>
<echo-modal ref="el" title-id="x-title" size="md"><template #title>…</template>…</echo-modal>
<echo-drawer ref="el" title-id="y-title">…</echo-drawer>
useToast().push({ tone: 'success', title: 'Done', message: 'Saved.' })
<echo-block :loading="pending" label="Loading…">…</echo-block>
<echo-loader :loading="loading">…</echo-loader>
<echo-skeleton :lines="3" /> · <echo-spinner size="sm" />
<echo-progress :value="64" label="Progress" /> · <echo-ring :value="76" />

Content

<echo-card title="Team" subtitle="Who works here" :loading="pending">…</echo-card>
<echo-list><echo-list-item title="Row" /></echo-list>
<echo-data-table :rows="ROWS" :columns="COLS" />
<echo-avatar :src="url" name="Ada Lovelace" size="lg" />
<echo-gallery :items="IMAGES" />
<echo-tree :nodes="NODES" /> · <echo-timeline :items="STEPS" />
<echo-collapse><template #summary>…</template>…</echo-collapse>
<echo-stat label="Visits" value="24.8k" :delta="12.4" />
<echo-player :src="video" />

Media and codes

<echo-chart :option="option" summary="What the chart says" height="280px" />
<echo-map :lat="-25.5" :lng="-54.6" :zoom="12" :markers="PINS" />
<echo-carousel label="Slides" loop autoplay>…</echo-carousel>
<echo-editor v-model="html" label="Body" />
<echo-image-cropper ref="el" :usages="['avatar']" mask="square" @apply="upload" />
<echo-image-viewer ref="el" :src="url" :caption="url" />
<echo-qr-code value="https://example.com" :size="180" />
<echo-barcode value="ECHO-2026-0001" format="CODE128" />

Calendar

<echo-calendar v-model:view="view" v-model:date="day" :events="EVENTS" @create="onCreate" @open="onOpen" />
<echo-calendar-mini v-model="day" :events="EVENTS" />

Account modals

<auth-login-form :form-id="id" :pending="sending" :error="failure" @submit="signIn" />
<auth-register-form :form-id="id" @submit="register" @login="toLogin" />
<auth-forgot-password-form :form-id="id" :sent="sent" @submit="recover" />
<auth-update-password-form :form-id="id" require-current @submit="changePassword" />
<auth-page-shell :title="title" :image="COVER" cover-title="…">…</auth-page-shell>
<auth-login-modal ref="el" :pending="sending" :error="failure" @submit="signIn" />
<auth-register-modal ref="el" @submit="register" @login="switchToLogin" />
<auth-forgot-password-modal ref="el" :sent="sent" @submit="recover" />
<auth-update-password-modal ref="el" require-current @submit="changePassword" />
<auth-lock-modal ref="el" :name="user.name" :avatar="user.avatar" @submit="unlock" />

Chrome (bars, footers, rails)

<masthead-ecommerce :links="LINKS" :departments="DEPARTMENTS" :cart-count="3" @search="onSearch" />
<masthead-learn :links="LINKS" :course="course" :done="17" :total="24" />
<masthead-help-desk :links="LINKS" :status="status" status-tone="success" :ticket-label="label" />
<masthead-landing :links="SECTIONS" :cta-label="cta" floating />
<footer-ecommerce :columns="COLUMNS" :legal="LEGAL" phone="…" email="…" />
<footer-learn :columns="COLUMNS" :cta-title="…" :cta-label="…" /> · <footer-landing :links="LINKS" :tagline="…" />
<sidebar-panel v-model:collapsed="collapsed" :groups="GROUPS" />
<sidebar-docs :groups="NAV" content-selector="#guide" /> · <sidebar-filters v-model="picked" :groups="FILTERS" />
<element-theme-switch /> · <element-language-switch compact /> · <element-currency-switch />

Account modals

Sign in, create account, recover and change the password, and the locked session. Five ready components in app/components/auth/, over the same echo-modal.

All five follow the same contract: open() and close() exposed by ref, one submit with the data, and no network calls inside. The application knows the URL, the token and what to do with the response - the component only collects, validates what doesn't depend on the server, and hands it back.

<auth-login-modal
  ref="login"
  :pending="sending"
  :error="failure"
  logo="/img/minha-marca.svg"
  title="Entrar no painel"
  @submit="signIn"
  @forgot="forgot?.open()"
  @register="register?.open()"
/>

login.value?.open()

The submit comes out in the API's vocabulary, not the internal variables': sending the payload straight to the endpoint is the common case, and renaming fields in every derived site would be the same mapping written many times.

Account modal contract
ComponentSubmit fieldsEvents
<auth-*-form>os mesmos campos do modal correspondente@submit + os do fluxo
<auth-login-modal>email, password, remember@submit @forgot @register
<auth-register-modal>name, email, gender (m|f|o), birth_date (yyyy-mm-dd), password, password_confirmation, terms (1|0)@submit @login
<auth-forgot-password-modal>email@submit @login
<auth-update-password-modal>password, password_confirmation@submit
<auth-lock-modal>password@submit @sign-out

The props are the same across all five, with two noted exceptions. All are optional.

logo

Header artwork, when the modal stands for a brand other than the site's. Without it, the site logo.

title

Your own title instead of the flow's default text.

pending

Locks the fields and swaps the button label while your call runs.

error

Server message, shown in an alert above the form. Whoever passed it clears it.

sent

Recovery modal only: swaps the form for the sent confirmation. It belongs to the caller, because whoever made the request is who knows it arrived.

name / avatar

Lock modal only: name and portrait of whoever's session is locked.

See all five running

Forms and collapses

Fields over the lens ink and the native details as a card.

es-input covers input and textarea; es-select is the native select with the same ink. es-collapse dresses the browser's details: it opens, closes and stays accessible without a line of JS.

<echo-input v-model="email" type="email" />

<echo-textarea v-model="message" rows="3" />

<echo-select v-model="topic">…</echo-select>

<echo-collapse>
  <template #summary>…</template>
  …
</echo-collapse>
See it in the showcase

Bars, footers and rails

The chrome of the four kinds of site the group builds, ready to wear.

The shape of the chrome is a derived site’s first decision and the most expensive to undo later: a shop needs search in the middle of the first strip, a course needs the reader’s progress, a help desk needs the service’s state and a landing page needs one CTA with nothing competing. Starting from the nearest of the four is cheaper than bending the generic bar into it.

<masthead-ecommerce
  :links="LINKS"
  :departments="DEPARTMENTS"
  :cart-count="cart.length"
  flat
  @search="goToListing"
/>

<footer-ecommerce :columns="COLUMNS" :legal="LEGAL" />

The five bars and the five footers follow the same contract, and it is what lets a site change variant without rewriting the page.

Four kinds, not a theme

Shop, course, help desk and landing page. They are not skins of one bar: what changes is what occupies the middle of the first strip.

Labels come in as props

No variant holds text: the links, the departments and the columns arrive ready and translated. A bar with names written into it is a bar that gets edited instead of used.

Two skins, one decision

The default is no capsule; `lens` brings it back, for a bar over artwork. The icon buttons and the menus change together (`useBarSkin`): mixing the two is worse than either.

embedded for samples

Drops the `sticky` and nothing else, for mounting a bar inside a page. It is what makes the showcase samples the real bars rather than drawings of them.

Rules

The conventions that hold for every derived site.

CSS in one place

No style blocks in .vue: utilities in the template and everything else in main.css.

A token for every themeable value

Raw hex in a template is a finding; the whole brand lives in the MARCA block.

The es- prefix

Every system component class and token uses the prefix, renameable in one global replace.

Contrast in both themes

All text checked against the real field; accent color is not text color.

Motion and transparency

prefers-reduced-motion and prefers-reduced-transparency respected by every animation and every glass.

Locales in lockstep

Canonical Spanish; every interface string through t(), contracts stay literal.

Guide for AI agents

A downloadable document covering the whole system, written to be handed to an agent along with the task.

It carries the rules that apply before any line of code (colour only through tokens, two axes never combined by hand, native element first, text through t(), a component that does not fetch) and the catalogue of every family with its tag, props, events and verbatim snippets.

Every showcase page also has its own version, carrying that page’s family alone: it is the file to hand over when the task is one screen, not a site.

/agents/echosistema-ui.md      # the whole system
/agents/buttons.md             # one family per file
/agents/navbars.md
/agents/calendar.md             …

Help

A question the documentation does not cover?

Talk to the EchoSistema group at echosistema.online.

Changelog

What changed in the template, most recent first.

  • Theme width: The content ruler went from two values to three (compact, extended, full), with the deployment’s default coming from NUXT_THEME_WIDTH / NUXT__THEME_WIDTH and baked into the pre-paint script.
  • Bars, footers and rails: Five mastheads and five footers (shop, property portal, course, help desk, landing), three side rails and the switchers page, with a phone preview in a real viewport.
  • Account in two shapes: The fields moved out of the modals into components of their own, and the four routines gained real routes with no masthead, as a centred card or as a split with artwork over 60% of the screen.
  • Calendar: An agenda in four views, a small month for the rail and the date arithmetic in utilities - no new dependency, over the theme’s own tokens.
  • Showcase redistributed: Buttons and labels split into separate pages, an authentication group, a navigation group carrying the chrome and a page of whole forms.
  • One API origin: The Reference publishes the deployment’s origin and nothing else, and the test panel fires at it: the server select was a second source of truth.
  • Agent guide: Downloadable documents under /agentes: the whole system in one file and one version per showcase page.

  • Split showcase: The single eight-hundred-line page became six routes with a sidebar, one per subject.
  • Navigation: Pagination, breadcrumb, tabs with a real ARIA tablist, timeline and drawer.
  • Dashboard primitives: Progress bar, ring and stat row; dashboard cards became composition of those.
  • Card and list: Card with header, media and the four actions (collapse, refresh, expand, remove), plus List in three densities.
  • Media player: Plyr as the first runtime dependency, for Vimeo and YouTube behind a single interface.
  • Toasts: A global queue with the position in state, and the same accent wedge as alerts and modals.
  • Rating: Read-only with fractions and interactive over native radios, with keyboard arrows for free.
  • Controls and scale: Toggler, checkbox, radio, native date and color pickers, and the sm/md/lg button scale.
  • Intent system: Ten intents with variants for buttons, chips, badges and pills, on two class axes.
  • Alerts in eight tones: The four named tonal ones join the palette, with the rounded accent wedge.
  • Native modals: Opaque dialog with an intent wedge, blurred background, width scale and locked scrolling.
  • Showcase and typography: The /componentes and /tipografia pages with every element of the system.
  • Three families: Manrope, Montserrat and JetBrains Mono, each with its fixed role in the system.
EchoSistema TemplateBack to the site

The visual foundation of the EchoSistema group's platform sites.