Getting Started

Ejecting Components

Copy a specific eds-next component or block into your own repository for full customization.

When to use this

Install @mercadoeletronico/eds-next normally for everyday use. Use ejection only when a specific component or block needs customization beyond what its props and slots allow. Once ejected, your team owns that code — it no longer receives updates from the design system.

One-time setup

If your project does not have a shadcn-vue setup yet, avoid the interactive init — with no extra flags, or with -d/--defaults, it auto-selects a preset (e.g. nova) that installs shadcn-vue's own base theme: a full set of unused CSS custom properties (--card, --popover, --sidebar-*, --chart-1..5, etc.) that no eds-next component reads. Pass explicit flags instead — this skips the preset entirely and keeps your CSS entrypoint to just the Tailwind import:

npx shadcn-vue@latest init -y -t nuxt --base reka --style nova --icon-library lucide \
  --font geist-sans --base-color neutral --no-base-style

(swap -t nuxt for -t vite on a plain Vue + Vite project — the rest of the flags are the same. --style/--icon-library/--font/--base-color can be any supported value; they only affect components.json metadata, not the CSS output, once --no-base-style is set.)

Ejecting a component still adds a small amount of CSS boilerplate on top of your bare @import "tailwindcss"; — a font import, @import "tw-animate-css";, and a @custom-variant dark (&:is(.dark *)); line. Install the one missing dependency this introduces:

pnpm add tw-animate-css

You may also see a leftover block like this, left over from the base-style skip:

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
}

Delete it. It references --border/--background/--foreground/--ring, none of which are defined without the base style — Tailwind v4 fails the build with Cannot apply unknown utility class if you leave it in.

Then register the eds-next registry as a namespace in your components.json:

{
  "registries": {
    "@mercadoeletronico": "https://eletro.design/r/{name}.json"
  }
}

If your project uses pnpm 10+, run this once before your first add command:

pnpm approve-builds --all

Without it, shadcn-vue add fails with a scary ERR_PNPM_IGNORED_BUILDS error. It's harmless — pnpm 10+ blocks dependency build scripts until you approve them — but it looks like the eject itself failed.

Nuxt projects: register the @nuxt/ui module

Register the @nuxt/ui module before ejecting anything that renders a U* component (e.g. login-form). This is required even if your project already has @mercadoeletronico/eds-next installed via npm — the eds-next Nuxt module only registers its own Me* components, it does not register @nuxt/ui for you:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/ui']
})

Without it, @nuxt/ui's components (UAuthForm, etc.) are never registered — they won't appear in .nuxt/components.d.ts and templates that use them will fail to resolve.

Color tokens

Add the eds-next color tokens once per project:

npx shadcn-vue@latest add @mercadoeletronico/theme

This creates ~/assets/eds-tokens.css and installs @nuxt/ui as an npm dependency. The file's first line is @import "@nuxt/ui"; — that's what defines the semantic utility classes (bg-default, text-highlighted, border-accented, etc.) that ejected components like tooltip, spinner, and login-form render with. Registering the Nuxt module above is not a substitute for this import — the module wires up components/auto-imports, but the CSS chain still has to be imported explicitly, the same way it does for a plain Vite project.

Import it once in your global CSS entrypoint (e.g. app/assets/css/main.css on a project created by shadcn-vue init):

@import "../eds-tokens.css";

The relative path depends on where your CSS entrypoint actually lives relative to app/assets/eds-tokens.css — adjust it if your entrypoint isn't one level down, at app/assets/css/.

On a fresh Nuxt 4 project (which uses srcDir: 'app' by convention), the file lands at assets/eds-tokens.css in the project root, not app/assets/ — the shadcn-vue CLI's ~ alias doesn't read Nuxt's srcDir config. Move it into app/assets/eds-tokens.css yourself, or Nuxt won't bundle it.

Primary color

app.config.ts is fully yours — the CLI never ships or overwrites it. To match eds-next's own primary color, set it yourself:

// app.config.ts
export default defineAppConfig({
  ui: {
    colors: { primary: 'blue' }
  }
})

Plain Vue + Vite projects only

A bare npm create vite@latest -- --template vue-ts project has no Tailwind CSS and no @ path alias configured — both are required before shadcn-vue init -t vite will proceed. Add Tailwind v4:

pnpm add tailwindcss @tailwindcss/vite
// vite.config.ts
export default defineConfig({
  plugins: [tailwindcss(), vue()]
})
/* src/style.css */
@import "tailwindcss";

Then add a @ alias in both tsconfig.json and vite.config.ts:

// tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
// vite.config.ts
export default defineConfig({
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Nuxt projects register @nuxt/ui components via the Nuxt module (see "Nuxt projects: register the @nuxt/ui module" above). Plain Vite projects have no such module, so register the Nuxt UI Vue plugin manually instead — do this once, before ejecting any component that renders a U* component (e.g. login-form):

// main.ts
import ui from '@nuxt/ui/vue-plugin'

app.use(ui)
// vite.config.ts
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [ui()]
})

Ejecting a component or block

npx shadcn-vue@latest add @mercadoeletronico/tooltip
npx shadcn-vue@latest add @mercadoeletronico/spinner
npx shadcn-vue@latest add @mercadoeletronico/login

Adding login pulls in login-form and use-i18n automatically — they are declared as registryDependencies of the login and login-form items. This is expected: MeLoginForm bundles the full eds-next translation composable, including all 7 shipped locales, even if you only render the login form in one language. Ejecting login always brings the full login-form + use-i18n bundle with it — there's no lighter option today. If you only need the split-layout page and want to keep using the npm package's MeLoginForm for everything else, don't eject login at all; build the layout yourself instead.

Available items

Almost every component and block in @mercadoeletronico/eds-next is ejectable — the registry is generated from the same source used to build the npm package, so there is no separate list to keep in sync. This also includes composables — alongside use-i18n (see above), useTheme is ejectable as use-theme:

npx shadcn-vue@latest add @mercadoeletronico/use-theme

Browse what's available at /r/{name}.json for any component/block name shown in the components or blocks docs (convert the name to kebab-case, dropping the Me prefix — e.g. MeButtonBarbutton-bar). A small number of sub-components documented only as part of a parent component's page aren't ejectable as their own item — the registry only generates an item when a component has its own dedicated docs page to source a title and description from. MeForeheadCartArea and MeForeheadActionBar (documented on Forehead's own page, not individually) fall in this category and aren't delivered by any item today. MeForeheadBadge is documented the same way, but it IS delivered: it ships as part of the forehead item itself, since MeForehead's own template renders it directly. Ejecting forehead gets you MeForeheadBadge too — it just isn't ejectable as its own standalone item.

Known limitations

Component name collisions

If your project already has @mercadoeletronico/eds-next installed via npm and you eject a component that also exists in the npm package (e.g. spinner), the ejected file keeps its original internal name — defineOptions({ name: 'MeSpinner' }) — identical to the npm package's own component. There's no file-path collision, but the shared name can still cause ambiguity in Vue devtools, <KeepAlive include="MeSpinner">, or any code that references that literal name string.