npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@teiler/vue

v0.2.0

Published

Readme

Teiler 🪡 (ˈteɪlər - tailor)

Discord

Teiler is an open source library that simplifies the creation of stylish components for various frameworks.

Currently in the alpha phase, the library is actively being developed and improved.

Join our community on our Discord Server to stay informed about the latest developments, exchange ideas, and connect with fellow developers. We are continuously working on expanding our support to include more frameworks, allowing developers to effortlessly create components across various environments.

Example

import { component } from '@teiler/vue'

const Button = component.button<{
  _primary: boolean
}>`
  display: inline-block;
  border-radius: 4px;
  font-size: 0.8rem;
  line-height: 1.5rem;
  background: transparent;
  box-shadow: 0 0 0 3px #CBCBCB inset;

  ${({ _primary }) =>
    _primary && `
      color: #fff;
      box-shadow: none;
      background: #CBCBCB;
    `
  }
`

Keyframes

import { component, keyframes } from '@teiler/vue'

const bouncing = keyframes`
  from, 20%, 53%, 80%, to {
    transform: translate3d(0,0,0);
  }

  40%, 43% {
    transform: translate3d(0, -40px, 0);
  }

  70% {
    transform: translate3d(0, -15px, 0);
  }

  90% {
    transform: translate3d(0,-4px,0);
  }
`

const Button = component.button<{}>`
  animation: ${bouncing} 1s ease infinite;
  display: inline-block;
  border-radius: 4px;
  font-size: 0.8rem;
  line-height: 1.5rem;
  background: transparent;
  box-shadow: 0 0 0 3px #CBCBCB inset;
`

Global styles

global creates a component that renders nothing and adds its styles to the document. Props and the theme are interpolated the same way as in components:

import { global } from '@teiler/vue'

const GlobalStyles = global<{ _background: string }>`
  body {
    margin: 0;
    background: ${({ _background }) => _background};
  }
`
<template>
  <GlobalStyles _background="#fafafa" />
</template>

Styles are added when the component renders and stay in the document after it unmounts.

css

Interpolated functions can return a plain string, as in the examples above. Return css instead when the styles interpolate a component, keyframes or another function: a plain template string turns ${Icon} into [object Object] (@teiler/unplugin reports it as a warning).

import { component, css } from '@teiler/vue'

const Icon = component.span`
  color: gray;
`

const Button = component.button<{ _active: boolean; _color: string }>`
  color: black;

  ${({ _active, _color }) =>
    _active &&
    css`
      color: ${_color};

      ${Icon} {
        color: ${_color};
      }
    `}
`

A css template can also be interpolated directly, e.g. styles shared by several components: ${shared}. Functions inside it get the props of the component.

Extending

Pass an existing component to component to add styles to it. The new component keeps the element of the extended one, unless you pick another element with component.<tag>:

import { component } from '@teiler/vue'

const Button = component.button`
  display: inline-block;
  border-radius: 4px;
`

// renders <button>
const PrimaryButton = component(Button)`
  background: #CBCBCB;
`

// renders <a> with Button styles
const ButtonLink = component.a(Button)`
  text-decoration: none;
`

as prop

Use as to render a component as a different element or another component without creating a new styled component. Styles stay the same, as is not passed to the rendered element:

<script setup lang="ts">
  import { RouterLink } from 'vue-router'
  import { Button } from './components'
</script>

<template>
  <Button as="a" href="/docs">Docs</Button>
  <Button :as="RouterLink" to="/home">Home</Button>
</template>

When as is a component, slots are passed to it and style classes are applied through attribute fallthrough, so the component has to render a single root element and must not set inheritAttrs: false.

Props are typed from as: as="a" accepts anchor attributes, :as="RouterLink" accepts the props of RouterLink and requires its required props. Without as, the component accepts the attributes of its own element. vue-tsc reports an unknown tag in as and missing required props; with strictTemplates it also reports attributes the target does not accept:

<template>
  <Button as="a" href="/docs">Docs</Button> <!-- ok -->
  <Button href="/docs">Docs</Button>        <!-- error with strictTemplates: href is not a button attribute -->
  <Button :as="RouterLink">Home</Button>    <!-- error: to is required -->
</template>

The rendered DOM element is exposed as element, use a template ref to access it:

<script setup lang="ts">
  import { useTemplateRef } from 'vue'
  import { Button } from './components'

  const button = useTemplateRef('button')
  const element = button.value?.element // HTMLElement | null
</script>

<template>
  <Button ref="button">Submit</Button>
</template>

withComponent

Use withComponent to create a new styled component with the same styles that always renders another element or component. The original component stays unchanged:

import { RouterLink } from 'vue-router'
import { component } from '@teiler/vue'

const Button = component.button`
  background: #f18805;

  &.router-link-active {
    text-decoration: underline;
  }
`

const FooterLink = Button.withComponent(RouterLink)
const ButtonAnchor = Button.withComponent('a')
<template>
  <FooterLink to="/home">Home</FooterLink>
  <ButtonAnchor href="/docs">Docs</ButtonAnchor>
</template>

Props are typed from the target, so FooterLink requires to. The target component has the same requirements as a component passed to as. as still works and takes precedence over the target. Extending with component(FooterLink) keeps rendering RouterLink, component.a(FooterLink) renders <a>.

Theme

// theme.ts
export type CustomTheme = {
  fontColor: string
}
<!-- App.vue -->
<script setup lang="ts">
  import type { CustomTheme } from './theme'

  import { ref } from 'vue'
  import { ThemeProvider } from '@teiler/vue'
  import { Component } from './components'

  const theme = ref<CustomTheme>({ fontColor: 'red' })
</script>

<template>
  <ThemeProvider :theme="theme">
    <Component>Some test text</Component>
  </ThemeProvider>
</template>
// components.ts
import { component } from '@teiler/vue'

const Component = component.div`
  color: ${({ theme }) => theme.fontColor};
`

export { Component }

To type the theme in TypeScript, extend DefaultTheme from @teiler/vue in a declaration file (d.ts). Do not extend @teiler/core: it is a dependency of @teiler/vue, not of your app, so with pnpm TypeScript cannot resolve it and ignores the extension without an error.

import type { CustomTheme } from './theme'

declare module '@teiler/vue' {
  export interface DefaultTheme extends CustomTheme {}
}

SSR

To generate all styles at Server Side Rendering, you need to provide the style sheet before the app renders and dump all styles at the end. The dump method generates only the CSS used during rendering.

// provide:
import { createStyleSheet } from "@teiler/vue"
const styleSheet = createStyleSheet({})

provide('STYLE_SHEET', styleSheet)

// dump:
styleSheet.dump()

NuxtJS

To use it in NuxtJS you need to create a plugin:

import { createStyleSheet } from "@teiler/vue"

export default defineNuxtPlugin({
  name: "teiler",
  enforce: "pre",
  async setup(nuxtApp) {
    const styleSheet = createStyleSheet({})
    nuxtApp.vueApp.provide('STYLE_SHEET', styleSheet)

    if (process.server) {
      useHead(() => {
        return ({
          style: [{ children: styleSheet.dump(), type: 'text/css', 'data-teiler': true }],
        })
      })
    }
  },
  env: {
    islands: true,
  },
});

Hydration

This method allows you to pre-fill the cache with specific style IDs, optimizing performance by avoiding redundant insertions. Here's how you can use it with NuxtJS:

import { createStyleSheet } from "@teiler/vue"

export default defineNuxtPlugin({
  name: "teiler",
  enforce: "pre",
  async setup(nuxtApp) {
    const styleSheet = createStyleSheet({})
    nuxtApp.vueApp.provide('STYLE_SHEET', styleSheet)

    if (process.server) {
      useHead(() => {
        const { css, ids } = styleSheet.extract()

        return ({
          style: [{ children: css, type: 'text/css', 'data-teiler': ids.join(' ')}],
        })
      })
    } else {
      const element = document.querySelector('style[data-teiler]')
      if (element) {
        const ids = element.getAttribute('data-teiler')?.split(' ') || []
        styleSheet.hydrate(ids)
      }
    }
  },
  env: {
    islands: true,
  },
});

Content Security Policy

With a strict style-src policy, pass a nonce to the style sheet and provide it to the app. Without an explicit nonce no attribute is set, and the default style sheet never has one.

On the server, extract returns the nonce, so it can be set on the rendered <style> tag:

import { createStyleSheet } from "@teiler/vue"

const styleSheet = createStyleSheet({ nonce })
app.provide('STYLE_SHEET', styleSheet)

// after render:
const { css, ids, nonce } = styleSheet.extract()
const styleTag = `<style data-teiler="${ids.join(' ')}" nonce="${nonce}">${css}</style>`

On the client, read the nonce from the server rendered tag before creating the style sheet:

import { createStyleSheet } from "@teiler/vue"

const element = document.querySelector<HTMLStyleElement>('style[data-teiler]')
const styleSheet = createStyleSheet({ nonce: element?.nonce })
app.provide('STYLE_SHEET', styleSheet)

[!NOTE] Use the nonce property, not getAttribute('nonce'). Browsers hide the attribute when the policy is sent in a header, so getAttribute returns an empty string.

Build plugin

Teiler works without a build step. For smaller bundles, add @teiler/unplugin to Vite, Rollup, Rolldown, webpack, Rspack or esbuild. In production builds it minifies the CSS in templates and lets the bundler remove unused styled components. In every mode it gives components stable ids, so a ${Button} selector never matches another component with the same template strings, in development names their classes after their variables (Button-t1x2y3z), and warns about mistakes, such as CSS that is ignored at runtime. Without the plugin, set an id by hand with component.button.withConfig({ componentId: 'button' }).

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import teiler from '@teiler/unplugin/vite'

export default defineConfig({
  plugins: [vue(), teiler()],
})

With SSR, use it in both the server and the client build. See the plugin README for other bundlers, options and development mode.

Sew a Pattern

This tool simplifies the creation of consistent and reusable visual styles for components across various web frameworks. It provides a pattern-based approach, where patterns serve as blueprints for defining the visual style of components.

Example

// Pattern file in ui kit library
import { pattern } from '@teiler/core'

const ButtonPattern = pattern.button`
  display: inline-block;
  border-radius: 4px;
  font-size: 0.8rem;
  line-height: 1.5rem;
  background: transparent;
  box-shadow: 0 0 0 3px #CBCBCB inset;
`

export default ButtonPattern

// Usage of Pattern
import { ButtonPattern } from 'some-uikit-library'
import { createComponent, sew } from '@teiler/vue'

const Button = sew(ButtonPattern, createComponent)

export default Button

Patterns can be extended the same way as components. pattern(ButtonPattern) keeps the element of the extended pattern, pattern.<tag>(ButtonPattern) changes it:

import { pattern } from '@teiler/core'

const PrimaryButtonPattern = pattern(ButtonPattern)`
  background: #CBCBCB;
`

const ButtonLinkPattern = pattern.a(ButtonPattern)`
  text-decoration: none;
`