Skip to content

FAQ & troubleshooting ​

Installation ​

The UI has no colors and uses the system font ​

You haven't imported the CSS. Add it once at your app's entry point:

ts
import '@thl-ui/styles/thl-ui.css';

Angular: add node_modules/@thl-ui/styles/dist/thl-ui.css to styles in angular.json. Also add the thl-body class to <body> to get the font and background color.

npm install reports a peer dependency error ​

thl-ui requires Vue ≥ 3.5.2, React ≥ 18 or Angular ≥ 20. Older versions are not supported: Angular 19 can't compile the library, and Vue < 3.5.2 produces TypeScript type errors. Upgrade your framework, or if you really must, install with --legacy-peer-deps (not recommended).

TypeScript can't find component types ​

Use "moduleResolution": "Bundler" (or "NodeNext") in tsconfig.json. The legacy "node" mode can't read the package's exports field.

Do I need Bootstrap, jQuery or Google Fonts? ​

No. thl-ui's CSS is written entirely from scratch, and the Open Sans font is bundled in @thl-ui/styles (self-hosted, no external requests).

Theming ​

Change the brand color ​

Override CSS variables after importing thl-ui.css:

css
:root {
  --thl-theme: #7c3aed;        /* background of buttons, active tabs… */
  --thl-on-theme: #fff;        /* text placed on that color */
  --thl-theme-text: #6d28d9;   /* that color when used as text on white */
}

Keep a contrast ratio of at least 4.5:1 between --thl-on-theme and --thl-theme, and between --thl-theme-text and white. See Theme & dark mode.

Change the theme for just one area ​

html
<section data-thl-theme="red" data-thl-color-mode="dark">…</section>

My CSS is overridden by the library CSS (or vice versa) ​

Every class is prefixed with thl-, and the default link color has zero specificity (:where(...)), so a normal class selector of yours will win. To change the prefix and avoid clashes: set prefix: 'my' in the config and rebuild the SCSS with $prefix: my (@use "@thl-ui/styles/scss/index" with ($prefix: "my")).

The bundle is too large ​

  • Vue: avoid the default app.use(ThlUI) (it registers every component). Use import { ThlUI } from '@thl-ui/vue' for configuration, then import only the components you need. Using only Button: ~120 KB instead of ~230 KB.
  • React and Angular: import individual components; the bundler tree-shakes the rest.
  • Per-component CSS: @thl-ui/styles/components/button.css… (remember to also add @thl-ui/styles/tokens.css and @thl-ui/styles/fonts.css).

Behavior ​

User settings (language, skin, dark mode) aren't remembered ​

Enable persist: true in the config. Data is stored in localStorage under the key thl-ui-config (to rename the key: persist: { key: 'my-app-ui' }). Note that saved values override the config values on the next load.

Toast or confirm() doesn't show up ​

AppLayout already mounts the hosts. If you don't use AppLayout, place these once at the app root:

vue
<ThlToaster />
<ThlConfirmHost />

(React: <Toaster /> and <ConfirmHost />; Angular: <thl-toaster /> and <thl-confirm-host />.) Without a ConfirmHost, confirm() falls back to the browser's window.confirm.

Clicking the sidebar menu reloads the whole page ​

The sidebar uses <a href> by default. Pass the link slot (Vue), the renderLink prop (React) or the thlMenuLink template (Angular) to use router links. See step 3 in the Tutorial.

Form errors don't show ​

Errors only appear after the first Submit. Each ThlField needs name, value and rules, and must be inside a ThlForm with :values. To show server-side errors, pass error="…" to ThlField.

Validation messages are still in English ​

Messages follow the current language. Check locale in the config, or the value saved in localStorage if persist is enabled. Languages without a translation fall back to fallbackLocale (default en).

Add a new language ​

ts
app.use(ThlUI, {
  locales: ['vi', 'en', 'ko'],
  messages: { ko: { common: { ok: '확인', cancel: '취소' } /* … */ } },
});

Untranslated keys fall back to fallbackLocale. Right-to-left languages (ar, he, fa, ur) automatically set dir="rtl" and the layout mirrors itself.

Accessibility ​

Icon-only buttons ​

Always provide a label for screen readers:

vue
<ThlButton icon="trash-2" aria-label="Delete" />

Inputs without a visible label ​

Wrap them in ThlField label="…" (the label is linked to the input automatically), or pass aria-label.

Still stuck? ​

Open the matching playground (npm run dev:vue / dev:react / dev:angular) and compare with the sample page source in apps/playground-*: every component has a working example there.