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

@tcarnaes/design-system

v0.6.9

Published

Vue 3 design system with tokens, Tailwind CSS 4, light/dark mode, and 50+ components.

Readme

@tcarnaes/design-system

Biblioteca de componentes Vue 3 com design tokens, Tailwind CSS 4 e suporte a light/dark mode.

Requisitos

  • Vue ^3.5.0 (peer dependency)
  • Node.js 18+

Instalação

npm install @tcarnaes/design-system vue

Configuração rápida

1. Importe os estilos

No entry point da aplicação (ex.: main.ts):

import { createApp } from 'vue'
import App from './App.vue'
import '@tcarnaes/design-system/styles.css'

createApp(App).mount('#app')

2. Use os componentes

<script setup lang="ts">
import { Button, Input, FormField } from '@tcarnaes/design-system'
</script>

<template>
  <FormField :label="'Email'">
    <Input v-model="email" :placeholder="'[email protected]'" />
  </FormField>

  <Button :variant="'primary'">Salvar</Button>
</template>

3. Dark mode (opcional)

Adicione a classe dark no <html> para usar os tokens escuros:

<html lang="pt-BR" class="dark">

Ou alterne em runtime:

document.documentElement.classList.toggle('dark', isDark)

Playground

O playground local (npm run dev) inclui:

  • Home — cards interativos por categoria (forms, layout, feedback, foundations)
  • Library — catálogo dos 58 componentes com descrição, snippet de uso e botão Abrir playground quando houver demo
  • Docs — instalação, toasts, DataTable, dark mode
  • 32 demos no drawer — Button, IconButton, Link, Input, Textarea, Label, DateInput, FormField, DataTable, Layout, Dialog, etc.; controles atualizam o snippet Usage em tempo real
  • Showcase — demos compostos (ex.: AI Chat), não exportados como componente da lib
  • i18n — inglês e português (pt-BR)

Subcomponentes da Library (ex.: TabPanel, AppLayout, PageSizeSelect) abrem o demo do componente pai via mapeamento interno.

Formulários

Input com ícone (composição)

Input e FormField não têm prop icon. Ícones entram no slot do FormField com um wrapper relative:

<script setup lang="ts">
import { Mail } from 'lucide-vue-next'
import { FormField, Input } from '@tcarnaes/design-system'
</script>

<template>
  <FormField :label="'Email'" :required="true">
    <template #default="{ id }">
      <div class="relative">
        <Mail
          :size="14"
          class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
        />
        <Input :id="id" v-model="email" :type="'email'" class="pl-9" />
      </div>
    </template>
  </FormField>
</template>

Switch vs Toggle

| Componente | Uso | |------------|-----| | Switch | Controle pill on/off (role="switch"), tamanho sm / md | | Toggle | Linha de preferência: label + Switch (setting row com borda) |

Input, Textarea, DateInput e Badge aceitam :size (sm / md / lg em inputs; Badgesm | md).

DateInput

Campo de data com calendário e formato por locale (ex.: dd/mm/aaaa em pt-BR):

<DateInput v-model="date" :locale="'pt-BR'" />

Card

Um único componente Card com slots — não há CardHeader / CardContent / CardFooter separados:

<Card :variant="'outlined'">
  <template #header>
    <h3 class="font-semibold">Título</h3>
  </template>

  Conteúdo principal.

  <template #footer>
    <Button :variant="'primary'">Salvar</Button>
  </template>
</Card>

Variantes: elevated | outlined | flat.

DataTable

Tabela completa com busca global, multi-sort (Ctrl+click / ⌘+click), filtros por coluna, paginação e seletor de linhas por página.

Colunas

import type { DataTableColumn } from '@tcarnaes/design-system'

const columns: DataTableColumn[] = [
  { key: 'name', label: 'Nome', sortable: true, filter: 'text' },
  {
    key: 'status',
    label: 'Status',
    sortable: true,
    filter: 'enum',
    filterOptions: [
      { label: 'Ativo', value: 'active' },
      { label: 'Inativo', value: 'inactive' },
    ],
  },
  { key: 'createdAt', label: 'Criado em', sortable: true, filter: 'date' },
]

Tipos de filtro: 'text' | 'date' (de/até) | 'enum' (multi-select). O ícone de filtro no header abre um popover por coluna.

Uso client-side

<script setup lang="ts">
import { DataTable, Lozenge } from '@tcarnaes/design-system'
import type { DataTableColumnFilters, DataTableSortEntry } from '@tcarnaes/design-system'
import { ref } from 'vue'

const columns = [/* ... */]
const rows = ref([/* ... */])
const page = ref(1)
const pageSize = ref(10)
const sortStack = ref<DataTableSortEntry[]>([])
const columnFilters = ref<DataTableColumnFilters>({})
</script>

<template>
  <DataTable
    v-model:sort-stack="sortStack"
    v-model:column-filters="columnFilters"
    v-model:page-size="pageSize"
    v-model:current-page="page"
    :columns="columns"
    :rows="rows"
    :row-key="'id'"
  >
    <template #cell-status="{ value }">
      <Lozenge :variant="'success'">{{ value }}</Lozenge>
    </template>
  </DataTable>
</template>
  • Clique no header: ordena uma coluna (ascdesc → limpa)
  • Ctrl+click (⌘+click no Mac): adiciona coluna ao multi-sort (sem limite)
  • Filtro: botão ao lado do sort em colunas com filter definido

Uso server-side

<DataTable
  v-model:search="search"
  v-model:current-page="page"
  v-model:page-size="pageSize"
  v-model:sort-stack="sortStack"
  v-model:column-filters="columnFilters"
  :columns="columns"
  :rows="rows"
  :total="total"
  :loading="loading"
  :server-side="true"
  :row-key="'id'"
  @request="loadFromApi"
/>

O evento @request envia DataTableRequestParams (page, pageSize, search, sortStack, columnFilters).

Toasts

Monte o ToastHost uma vez no layout raiz e dispare toasts pelo composable:

<script setup lang="ts">
import { ToastHost, useToast } from '@tcarnaes/design-system'

const toast = useToast()

function onSave() {
  toast.success('Alterações salvas.')
}
</script>

<template>
  <ToastHost />
  <Button :variant="'primary'" @click="onSave">Salvar</Button>
</template>

Posições disponíveis: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right.

toast.error('Falha ao salvar.', {
  position: 'bottom-center',
  dismissible: true,
})

Variantes: success | error | info | warning — fundo semântico opaco com borda na mesma cor.

Overlay

Popover e Tooltip

Ambos usam :variant (não appearance):

| Variant | Visual | |---------|--------| | outline | Fundo sólido (bg-popover) com borda neutra — padrão | | primary | Fundo sólido neutro, sem borda | | ghost | Fundo translúcido com backdrop-blur | | danger | Tinta destructive translúcida, texto claro |

<script setup lang="ts">
import { Button, Popover, Tooltip } from '@tcarnaes/design-system'
</script>

<template>
  <Popover :variant="'outline'">
    <template #trigger>
      <Button :variant="'outline'">Abrir</Button>
    </template>
    <template #content>
      <p class="mb-2 text-sm font-medium">Ações rápidas</p>
      <p class="text-xs opacity-80">Conteúdo ancorado ao trigger.</p>
    </template>
  </Popover>

  <Tooltip :content="'Dica útil'" :variant="'ghost'" :placement="'top'">
    <Button :variant="'outline'">Hover</Button>
  </Tooltip>
</template>

Drawer

Painel deslizante com :placement="'left' | 'right' | 'top' | 'bottom'" e :close-on-overlay="false" opcional.

Exemplos por categoria

Formulários

<script setup lang="ts">
import {
  Checkbox,
  DateInput,
  FormField,
  Input,
  Select,
  Switch,
  Toggle,
} from '@tcarnaes/design-system'
import { ref } from 'vue'

const name = ref('')
const date = ref('')
const role = ref('')
const terms = ref(false)
const notifications = ref(true)
</script>

<template>
  <FormField :label="'Nome'" :required="true">
    <Input v-model="name" />
  </FormField>

  <FormField :label="'Data'">
    <DateInput v-model="date" :locale="'pt-BR'" />
  </FormField>

  <FormField :label="'Função'">
    <Select
      v-model="role"
      :placeholder="'Selecione...'"
      :options="[
        { label: 'Designer', value: 'design' },
        { label: 'Engineer', value: 'eng' },
      ]"
    />
  </FormField>

  <Checkbox v-model="terms">Aceito os termos</Checkbox>
  <Toggle v-model="notifications">Notificações</Toggle>
  <Switch v-model="notifications" :size="'sm'" />
</template>

Feedback

<script setup lang="ts">
import { Alert, Badge, Progress, Skeleton, Spinner } from '@tcarnaes/design-system'
</script>

<template>
  <Alert :variant="'success'">Deploy concluído.</Alert>
  <Alert :variant="'error'" :dismissible="true">Falha na build.</Alert>
  <Badge :value="12" :variant="'primary'" :size="'md'" />
  <Lozenge :variant="'success'">Ativo</Lozenge>
  <Progress :value="72" />
  <Skeleton class="h-8 w-full" />
  <Spinner :aria-label="'Carregando'" />
  <!-- Sem glow: <Spinner :glow="false" :aria-label="'Carregando'" /> -->
</template>

Layout

AppLayout organiza as regiões da página (grid + painel lateral), mas não estiliza header, menu, conteúdo, panel ou footer. Use o slot #menu com SidebarMenuItem / SidebarMenuGroup — o shell e toggle com chevron já vêm montados. Com :settings-menu="true", declare um SidebarMenuGroup com id="settings" (ou settings-menu-id) ou um SidebarMenuItem sozinho com o mesmo id — veja o demo Layout no playground.

<script setup lang="ts">
import { ref } from 'vue'
import { AppLayout, Button, SidebarMenuGroup, SidebarMenuItem } from '@tcarnaes/design-system'

const menuCollapsed = ref(false)
const panelOpen = ref(false)
const menuWidth = ref('12rem')
const menuCollapsedWidth = ref('3rem')
const menuLabel = ref('Navigation')
const activeId = ref('dashboard')
const openKeys = ref<string[]>([])
const settingsMenu = ref(true)
const pageTitle = ref('Dashboard')
</script>

<template>
  <AppLayout
    v-model:menu-collapsed="menuCollapsed"
    v-model:panel-open="panelOpen"
    v-model:active-menu-id="activeId"
    v-model:open-menu-keys="openKeys"
    class="min-h-svh"
    :menu-width="menuWidth"
    :menu-collapsed-width="menuCollapsedWidth"
    :menu-label="menuLabel"
    :settings-menu="settingsMenu"
  >
    <template #header>
      <div class="flex items-center justify-between border-b border-border bg-card px-4 py-3">
        <h1 class="text-sm font-semibold text-foreground">My App</h1>
      </div>
    </template>

    <template #menu>
      <SidebarMenuItem :id="'dashboard'" :label="'Dashboard'" />
      <SidebarMenuGroup :id="'settings'" :label="'Settings'" flyout-placement="up">
        <SidebarMenuItem :id="'settings.profile'" :label="'Profile'" />
      </SidebarMenuGroup>
    </template>

    <div class="flex flex-col gap-4 p-6">
      <p class="text-sm text-muted-foreground">{{ pageTitle }}</p>
      <Button :variant="'outline'" :size="'sm'" @click="panelOpen = true">View details</Button>
    </div>

    <template #panel="{ closePanel }">
      <div class="flex flex-col gap-4 border-l border-border p-6">
        <button type="button" class="self-end text-xs text-muted-foreground hover:text-foreground" @click="closePanel()">
          Close
        </button>
      </div>
    </template>

    <template #footer>
      <div class="border-t border-border bg-card px-4 py-2 text-xs text-muted-foreground">
        Footer
      </div>
    </template>
  </AppLayout>
</template>

Settings no rodapé: com :settings-menu="true", declare no #menu um SidebarMenuGroup com id="settings" (ou settings-menu-id) ou um SidebarMenuItem sozinho com o mesmo id. Grupo → submenu no hover; item único → link simples no rodapé.

IDs do menu lateral

  • Itens de topo fora de um grupo não devem compartilhar o prefixo do grupo (evite todos.all ao lado do grupo todos — prefira all ou ids dentro do grupo, ex. todos.active).
  • Com v-model:active-menu-id vazio, o primeiro SidebarMenuItem registrado é selecionado automaticamente.
  • Flyouts perto do rodapé abrem para cima — use flyout-placement="up" no SidebarMenuGroup de settings ('auto' | 'down' | 'up').

O slot default (conteúdo) ocupa 100% da altura disponível — use class="min-h-svh" no AppLayout (ou height: 100% em html, body e #app). Header, panel e footer continuam compostos por você com classes Tailwind.

Ícones no Button

Variantes: default | primary | secondary | outline | ghost | destructive | clean | link · tamanhos: default | sm | md | lg | icon.

appearance ainda funciona em runtime como alias legado (dangerdestructive, primaryprimary, etc.), mas prefira :variant.

<script setup lang="ts">
import { Button } from '@tcarnaes/design-system'
</script>

<template>
  <Button :variant="'primary'" :size="'md'" :icon="'zap'">Action</Button>
</template>

Mais de 300 ícones disponíveis via iconography, buttonIcons e iconographySelectOptions.

Componentes exportados

| Categoria | Componentes | |-----------|-------------| | Actions | Button, IconButton, Link | | Forms | Input, DateInput, Textarea, Checkbox, Radio, RadioGroup, Switch, Toggle, Select, Label, FormField | | Feedback | Alert, Badge, Spinner, Progress, Skeleton, Toast, ToastHost | | Navigation | Tabs, TabList, Tab, TabPanel, Breadcrumb, BreadcrumbItem, Pagination, SidebarMenu* | | Data display | Card, Divider, Avatar, AvatarGroup, Lozenge, Table*, DataTable, DataTableColumnFilter*, PageSizeSelect, List, ListItem, EmptyState | | Overlay | Modal, Dialog, Tooltip, Popover, Drawer | | Layout | Container, Stack, Grid, AppLayout | | Utils | cn, useToast, buttonVariants, iconography |

Pagination inclui botões de primeira/última página (« »).

TypeScript

Tipos incluídos no pacote — não é necessário @types separado.

import type {
  ButtonIconName,
  DataTableColumn,
  DataTableColumnFilters,
  DataTableRequestParams,
  DataTableSortEntry,
  SelectOption,
  SortDirection,
  ToastPosition,
} from '@tcarnaes/design-system'

Desenvolvimento local

npm install
npm run dev

Playground em http://localhost:5173.

npm run build:lib        # biblioteca → dist/
npm run build:playground # playground estático (GitHub Pages)
npm run test             # Vitest

Licença

MIT