Install
pnpm add @hina-ui/vue
vue is a peer dependency and must satisfy ^3.5.0. Everything else is installed with the package and does not need to be declared.
Style entry
@import 'tailwindcss';
@import '@hina-ui/vue/styles/tokens.css';
tokens.css is the only style entry; no other file needs to be imported, and no @source line is needed for the library, since the entry already registers the paths to scan.
Fonts
Body text uses Noto Sans for Latin and Noto Sans SC for Chinese, and monospace uses JetBrains Mono. Loading them is the application’s responsibility.
When the root element’s lang is Chinese, Noto Sans SC comes first, so the Latin text on the page uses Noto Sans SC as well.
export default defineNuxtConfig({
modules: ['@nuxt/fonts'],
fonts: {
families: [
{ name: 'Noto Sans', provider: 'google', weights: [400, 500, 600, 700], global: true },
{ name: 'Noto Sans SC', provider: 'google', weights: [400, 500, 600, 700], global: true },
{ name: 'JetBrains Mono', provider: 'google', weights: [400, 500], global: true },
],
},
})
The font names live in the --hn-font-* variables, which automatic scanning cannot detect, so they must be declared explicitly with global set to true.
Mount at the root
<template>
<TooltipProvider>
<NuxtPage />
<Toaster />
</TooltipProvider>
</template>
Tooltip and Toast each need one root node, mounted once at the application entry. AppShell already includes TooltipProvider.
Single instances
vue and @hina-ui/vue must each resolve to a single instance within one application. Duplicates cause overlays to misbehave and focus management to break.
pnpm why vue @hina-ui/vue
Dark mode
<html lang="en" class="dark">
<!-- the whole document in dark mode -->
</html>
The dark class also works on a container within the page, switching the semantic colours inside it to dark. Overlays are teleported to body by default, outside the container, and follow the root element’s theme.
Dark mode is toggled by the dark class on the root element, and the semantic colours flip with it. With @nuxtjs/color-mode, set classSuffix to an empty string.
Density
<section data-density="compact">
<!-- every control inside this container tightens up -->
</section>
Control height, padding and spacing all come from the density variables, which default to comfortable. Density is declared on a container and applies to every control inside it.
<script setup lang="ts">
import { ref } from 'vue'
import { Search } from '@lucide/vue'
import { Button, IconButton, Inline, Input, Stack, Tag } from '@hina-ui/vue'
const compact = ref(false)
</script>
<template>
<Stack align="center">
<Button variant="outline" tone="neutral" size="sm" @click="compact = !compact">
{{ compact ? 'Back to default density' : 'Switch to compact' }}
</Button>
<Inline :data-density="compact ? 'compact' : undefined" align="center">
<Input placeholder="Search" />
<Button>Submit</Button>
<Button variant="outline" tone="neutral">Reset</Button>
<IconButton label="Search" variant="outline">
<Search />
</IconButton>
<Tag>Draft</Tag>
</Inline>
</Stack>
</template>
size and density are independent: size sets the size of a single control, density scales the whole group. Density does not follow the device type, so if a touch screen needs larger hit targets, use the default density explicitly.
Language
Three message packs are built in: zhCN, enUS and jaJP. zhCN is the default.
import { enUS, provideUiLocale } from '@hina-ui/vue'
provideUiLocale(enUS)
Built-in wording defaults to Simplified Chinese and is switched with one call at the application root. A partial object also works, overriding individual strings while the rest stay in Chinese.
<script setup lang="ts">
import { ConfigProvider } from '@hina-ui/vue'
</script>
<template>
<ConfigProvider dir="ltr">
<RouterView />
</ConfigProvider>
</template>
ConfigProvider sets the global text direction dir, which every component without its own dir inherits; teleportTo chooses the container that overlays mount into.
Verify the install
<script setup lang="ts">
import { Button, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack align="center" class="p-10">
<Button>Installed</Button>
</Stack>
</template>
If the button shows the accent background, changes colour on hover and ripples on press, both the dependencies and the styles are in place. Structure without styling usually means the style entry is not imported.