@eos-ui/vue
A component library for Vue. High contrast and consistent by default, rebrandable with a few lines.
Documentation, live examples and a theme playground
npm install @eos-ui/vue
import { createApp } from "vue";
import { createEos } from "@eos-ui/vue";
import "@eos-ui/vue/style.css"; // once, in your entry file
createApp(App)
.use(
createEos({
locale: "en-CA",
currency: "CAD",
theme: { brand: "#2c95b4", radius: "round" },
}),
)
.mount("#app");
<script setup lang="ts">
import { ref } from "vue";
import { ETextField, EButton } from "@eos-ui/vue";
const email = ref("");
</script>
<template>
<ETextField v-model="email" label="Email" type="email" required />
<EButton>Continue</EButton>
</template>
- One stylesheet (about 21 kB gzipped), imported once:
@eos-ui/vue/style.cssis required even without the plugin. All CSS ships in that file (no per-component CSS). The JavaScript has no CSS imports, so server rendering needs no bundler setup. - Requirements: ESM only, Node 22 or later, Vue 3.5 or later. Tree-shaking is done by your bundler. Your bundler must replace
process.env.NODE_ENVwith a literal, as Vite, webpack, Rollup, esbuild and Nuxt do by default. The library reads it to keep development warnings (a theme that fails contrast) out of production builds; with a bundler that does not define it, the warnings stay on, and you can turn the contrast one off withwarnOnContrast: false. - Fonts are opt-in:
@eos-ui/vue/fonts-self-hosted.cssserves the default typefaces from files in the package;@eos-ui/vue/fonts.cssloads them from Google Fonts, which shares visitors' IP addresses with Google;@eos-ui/vue/fonts-fallback.csshas only the metric-matched fallback faces. With none of them, the theme uses system fonts. - Everything sits in the cascade layers
e.*, so your own CSS wins without specificity fights. - Plugin options:
locale,currency,dir,messages,icons,theme,themeTarget,portalTarget,warnOnContrastandapplyTheme(falsewhen the page ships the theme CSS itself). Without athemenothing is applied:style.csscarries the default theme. Athemegoes into the document as a stylesheet in the token layer, so your own CSS wins over it.EConfigProviderchanges the theme, locale or direction for one part of the page. - A message can be a plural object (
{ one, other }and the locale's other categories) when it takes a countn.resultsCount,rows,rowsAnnounceandrangeDonedo. defineTheme()(re-exported from@eos-ui/tokens) builds a theme frombrand,accentand other inputs and checks its contrast.- Icons: a built-in Phosphor set, or Lucide, Tabler, Heroicons, Phosphor (Vue), Iconify or your own set (
@eos-ui/vue/icons, see Bring your own icons). - Per-component paths:
@eos-ui/vue/components/EButton(any component), and@eos-ui/vue/pluginforcreateEosandthemeCss. They leave out the built-in icon set, which the package root registers;import "@eos-ui/vue/icons/builtin"brings it back. EosResolverfrom@eos-ui/vue/resolverauto-imports components forunplugin-vue-components. It does not import the stylesheet unless you pass{ css: true }.- Importing the package types
$e(the config) in templates.createEos()registers no components, so their global types are opt-in: afterapp.component("EButton", EButton), addimport type {} from "@eos-ui/vue/globals"(or"types": ["@eos-ui/vue/globals"]intsconfig.json). Withunplugin-vue-componentsyou do not need it: the plugin writes its owncomponents.d.ts. - Composables for common needs:
useAnnouncer(screen reader announcements),useShortcuts(keyboard shortcuts),useCopy,useToast. See Composables. @eos-ui/vue/llms.txtandllms-full.txtgive AI coding assistants an index and the complete docs.- Components work without the plugin, in English and US dollars.
Nuxt
Import the stylesheet in nuxt.config.ts, install the plugin in a Nuxt plugin, and put the theme into the server-rendered <head> with themeCss(). createEos touches the document only in a browser: with a theme it adds the theme as a stylesheet once the app is installed, so without the <style> the first paint is unthemed.
// nuxt.config.ts
export default defineNuxtConfig({ css: ["@eos-ui/vue/style.css"] });
// plugins/eos.ts
import { createEos, defineTheme, themeCss } from "@eos-ui/vue";
const theme = defineTheme({ brand: "#2c95b4", radius: "round" });
export default defineNuxtPlugin((nuxtApp) => {
// The <style> below carries the theme on the server and in the browser, so the plugin adds no second copy.
nuxtApp.vueApp.use(createEos({ locale: "en-CA", currency: "CAD", theme, applyTheme: false }));
useHead({
style: [{ id: "eos-theme", innerHTML: themeCss(theme) }],
htmlAttrs: { lang: "en-CA", dir: "ltr" },
});
});
Components render on the server without a document. Build the theme once with defineTheme and give the same one to createEos and themeCss, so the server and the browser agree. applyTheme: false is not required: without it the browser also gets the plugin's own copy of the theme, scoped to :root[data-e-theme], which is the same tokens again. The plugin sets lang and dir on <html> only in a browser, so set them for the server with htmlAttrs.
Source, issues and contributing. MIT licensed.