sanhaua
| Install | |
|---|---|
npm install sanhaua |
|
| Latest Version: | 0.19.3 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Aug 18, 2026 |
| Links: | GitHub · npm |
Sanhauá

Design System named after the famous river in City of Parahyba.
Check out our live documentation.
Component structure
Sanhaua is organized to support multiple frameworks:
system/components/vue: Vue implementation and storiessystem/components/react: React implementationsystem/components/wc: Web Components implementationsystem/components/styles: Shared component styles used across frameworks
This allows each framework to keep its own implementation while reusing the same CSS source.
Usage
This section describes how to import Sanhauá as an external package and use it within your project
Install Sanhauá
npm i sanhaua
If you need to use any component, import them as usual like:
<!-- src/App.vue -->
<script>
import { uaButton } from 'sanhaua'
</script>
<template>
<ua-button type="button" width-behavior="full">Botaozin</ua-button>
</template>
Import Sanhauá's styling to your project it in you main.js file:
// src/main.js
import 'sanhaua/dist/style.css'
If you have a bundler like Vite, you can also use our design system tokens on your application. Just install Sass. You should be able to import all tokens and properties into your own project this way:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const SELECTED_THEME = 'main'
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler', // We use sass-embedded, but if you use normal sass, just replace this with 'modern'
additionalData: `
@use 'sass:map';
@import "sanhaua/system/themes/${SELECTED_THEME}/design-tokens/design-tokens.scss";
@import "sanhaua/system/themes/${SELECTED_THEME}/global/global.scss";
@import "sanhaua/system/themes/${SELECTED_THEME}/responsiveness/responsiveness.scss";
`
}
}
}
})
TypeScript
The React layer (system/components/react, lib/react.ts) is written in TypeScript and
ships generated declarations — dist/lib/react.d.ts and the per-component .d.ts
under dist/system/components/react/, built by vite-plugin-dts from the .tsx source
during npm run build. Prop types for UaAlert, UaButton, UaCard, UaCheckbox, UaInputField,
UaInputGroup, UaModal, UaRadio, UaSkeleton, UaTable, UaTabs, UaTextarea
and UaToast are therefore always in sync with the implementation — there is no
hand-maintained .d.ts to drift.
Vue and Web Components are still plain .vue/.js; their declarations
(types/vue.d.ts, types/wc.d.ts, types/sanhaua.d.ts) are written by hand and need to
be reviewed on every release that touches those components — the same caveat that used to
apply to the whole package.
Shared prop vocabulary (ButtonAppearance, FieldAppearance, Size, …) lives in
system/types/tokens.ts, read off the modifier classes that actually exist in
system/components/styles/*.scss — that SCSS is the source of truth, not any consumer's
copy of the types.
npm run typecheck runs tsc --noEmit over the TS layer.
Consuming the SCSS layer
This is the part every consumer has had to reverse-engineer from vite.config.ts trial
and error, so it is spelled out here in full. A Vite consumer needs, in this order:
// vite.config.js (consumer)
css: {
preprocessorOptions: {
scss: {
additionalData: `
@use "sass:map";
@import "sanhaua/system/themes/main/design-tokens/design-tokens";
@import "sanhaua/system/themes/main/responsiveness/responsiveness";
`,
// Sanhauá's own SCSS is entirely @import. additionalData is prepended to every
// file the consumer compiles, so a component .scss in the consumer's codebase
// can never open with @use — it would stop being the first statement in the
// file and Sass would abort. Silence the resulting deprecation warnings:
silenceDeprecations: ['import', 'global-builtin']
}
}
}
Notes:
@use "sass:map";first._media-queries.scssandsystem.scsscallmap.getwithout importing thesass:mapmodule themselves — they rely on the consumer's build having already loaded it globally. Omit this line and any@importthat pulls those files in fails.- Import the token files directly, not
system.scss. The recipe above never touchessystem/themes/system.scssor the@themealias it uses internally (@import "@theme/design-tokens/design-tokens") —@themeis a build-time alias configured in this repo's ownvite.config.js/vite.lib.config.jsand is not part of the published contract. A consumer that only needs tokens and component styles doesn't need to declare that alias at all; it's only relevant if you importsystem.scssitself (for the.sanhaua.light/.sanhaua.darkscope rules), in which case mirror the alias in your ownresolve.aliaspointing atsanhaua/system/themes/main. - Component styles are plain SCSS, not CSS custom properties. Colors are Sass maps
(
map-get($color-primary-light, "500")), spacing is a function (spacing(4)), and typography is mixins (@include body-1;). Nothing emitsvar(--...). - Wrap your markup in an element with
class="sanhaua light"(ordark) — every component style is scoped under.sanhaua, and the theme block picks the color set.
Breaking changes in 0.17.0
UaButtonnow requireschildren. An icon with no text isUaButtonIcon, which keeps the symmetric padding a square or circular button needs —UaButtonpads its sides more than its top and bottom, so an icon-onlyUaButtoncame out lopsided.UaInputFieldandUaSelectno longer defaultlabelto'Text'/'Select'. Omit it for a bare control and passaria-label; the wrapper degrades from<label>to<div>.- Button sizes shifted down one step:
mediumnow carries whatsmallused to, andlargewhatmediumused to. Padding is no longer uniform.
Known gaps
UaTableexists in React only. Vue and Web Components don't have a table component yet — no equivalent SCSS or story either.UaSelectandUaTextareareuse the.form-element-wrapper,.label,.hintand.error-messagerules declared instyles/ua-input-field.scss. The fields share that anatomy on purpose, but the sheets are coupled: removing the wrapper block from the input sheet would strip the select's and the textarea's label styling too.UaInputGroupdeliberately does not — it is afieldsetwith its ownlegend, and it declares its own hint and error rules so it stands alone.- The React
UaInputFieldcovers every inputtypein one component; Vue keeps the older pattern of one component per type (UaInputDate,UaInputEmail,UaInputPassword,UaInputTel,UaInputText), and onlyUaInputPasswordhas the show/hide toggle. These haven't been reconciled across frameworks. Because of that,styles/ua-input-field.scss(React) andstyles/ua-input-text.scss(Vue and Web Components) are near-identical duplicates today — same rules, different block class. Migrating Vue and Web Components to the unified format collapses the per-type sheets intoua-input-field.scss; the duplication is deliberate until then, and the note is repeated at the top of that file. UaAccordion,UaAlert,UaAvatar,UaBadge,UaButtonIcon,UaCard,UaCheckbox,UaInputGroup,UaModal,UaPagination,UaSelect,UaTabsandUaTextareaexist in React only.UaRadioexists in React and Vue. The SCSS instyles/is framework-agnostic, so the Vue and Web Component ports are implementation-only work.UaModalrenders in place — no portal — so it stays inside the.sanhaua.light/.sanhaua.darkscope that every sheet keys off. Mount it from a container that is not inside atransform,filterorcontainancestor, or the fixed overlay will be clipped to that ancestor instead of the viewport.UaButtonIconrenders<button>only. There is nobehavior="link"branch yet, so an icon that navigates still needs aUaButton.UaSelectopts into the customizable<select>(appearance: base-select, the inner<button>and<selectedcontent>, and the::picker(select)rules). That styling only lands in Chromium 135+; Firefox and Safari ignore the unknown value and the unknown children, falling back to the platform control with our border, padding and size. The fallback loses the leadingiconand the custom chevron, which live inside that button.
Development
Wanna play around with the design system in dev mode? Here's how to do it.
Recommended IDE Setup
Customize configuration
See Vite Configuration Reference.
Project Setup
npm install
Compile and Hot-Reload for Development
npm run dev
Compile and Minify for Production
npm run build
Run documentation in dev mode
npm run docs
Run Storybook composition host in dev mode
npm run docs:host
Run Vue Storybook instance for composition refs
npm run docs:vue
Build composed Storybook docs
npm run build-docs
Lint with ESLint
npm run eslint
or
npm run eslint:fix
to force automatic fixes.
Lint with Stylelint
npm run stylelint
or
npm run stylelint:fix
Format with Prettier
npm run format
Related Packages
Every c2n web component in one package: one import per component, JSX and Vue te...
Biblioteca de integração dos Web Components GovBR-DS com o Vue
Editor metadata for the c2n web components: VS Code / Volar HTML custom data and...
It's a simple routing system that changes the viewport depending on the route gi...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.19.3 | >=3.3.0 <4.0.0 | MIT | |
| 0.19.2 | >=3.3.0 <4.0.0 | MIT | |
| 0.19.1 | >=3.3.0 <4.0.0 | MIT | |
| 0.19.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.18.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.17.3 | >=3.3.0 <4.0.0 | MIT | |
| 0.17.2 | >=3.3.0 <4.0.0 | MIT | |
| 0.17.1 | >=3.3.0 <4.0.0 | MIT | |
| 0.17.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.16.1 | >=3.3.0 <4.0.0 | MIT | |
| 0.16.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.15.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.14.1 | >=3.3.0 <4.0.0 | MIT | |
| 0.14.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.13.0 | >=3.3.0 <4.0.0 | MIT | |
| 0.12.2 | >=3.3.0 <4.0.0 | MIT | |
| 0.12.1 | >=3.3.0 <4.0.0 | MIT | |
| 0.11.0 | ^3.5.11 | MIT | |
| 0.10.1 | ^3.5.11 | MIT | |
| 0.10.0 | ^3.5.11 | MIT | |
| 0.9.2 | ^3.5.11 | MIT | |
| 0.9.1 | ^3.5.11 | MIT | |
| 0.9.0 | ^3.5.11 | MIT | |
| 0.8.1 | ^3.5.11 | MIT | |
| 0.8.0 | ^3.5.11 | MIT | |
| 0.7.0 | ^3.5.11 | MIT | |
| 0.6.3 | ^3.5.11 | MIT | |
| 0.6.2 | ^3.5.11 | MIT | |
| 0.6.1 | ^3.5.11 | MIT | |
| 0.6.0 | ^3.5.11 | MIT |
Showing the latest 30 of 53. See every release on npm