Vue Tutorial

A step-by-step guide to using NYS Design System components in a Vue 3 application.

Installation

To start from scratch, create a new Vue Vite app:

Copy Code
npm create vue@latest

Install the two NYSDS packages in your app folder:

  • @nysds/vue for the Vue components and
  • @nysds/styles for the design tokens and global CSS.

Note: Both packages are versioned together. Always install matching versions to avoid token/component mismatches.

Copy Code
npm install @nysds/vue @nysds/styles

Project Setup

After installing, import the NYSDS stylesheet once, at the top of your entry file (src/main.ts):

Copy Code
import "@nysds/styles/full";

Without it, components render unstyled. @nysds/styles ships the design tokens and global styles. Component-level styles live in each component's shadow DOM and need no extra setup.

Optional agency theme: set <html data-theme="health"> (or admin, business, environment, local, safety, transportation). Fonts aren't bundled, so load them the way your agency normally does.

Your First Component

Copy Code
<script setup lang="ts">
import { NysAlert, NysButton } from "@nysds/vue";
const start = () => console.log("started");
</script>
<template>
  <NysAlert type="info" heading="Welcome" />
  <NysButton label="Start" @nys-click="start" />
</template>

Props, Events, and Slots

Props are set as DOM properties. Use either camelCase or kebab-case in templates: appName and app-name are the same prop. Bind numbers and booleans with : so they keep their type, not a string:

Copy Code
<NysPagination :total-pages="5" :current-page="1" />
<NysTextinput label="Name" required :disabled="locked" />

Events keep their full NYSDS names. Listen with @nys-change, @nys-input, and so on. The handler receives the typed event, so e.detail autocompletes:

Copy Code
<NysTextinput
  label="First name"
  @nys-input="(e) => console.log(e.detail.value)"
/>

Use the nys-* events instead of native @input or @change. They cover cases native events don't, like a combobox selection or a date picked from the calendar.

Slots work with <template #name>. The default slot renders as direct children:

Copy Code
<NysTextinput label="Email">
  <template #description>We'll never share it.</template>
</NysTextinput>
<NysTooltip text="Tooltip text">
  <NysButton label="Hover me" />
</NysTooltip>

Forms

v-model

Every form control supports v-model, bound to the property the component's form contract defines:

Component v-model value
NysTextinput, NysTextarea, NysSelect, NysCombobox, NysDatepicker, NysRadiogroup string
NysCheckboxgroup string[]
NysCheckbox, NysToggle boolean
Copy Code
<script setup lang="ts">
import { reactive } from "vue";
import {
  NysButton,
  NysCheckbox,
  NysCheckboxgroup,
  NysRadiobutton,
  NysRadiogroup,
  NysTextinput,
} from "@nysds/vue";
const model = reactive({
  firstName: "",
  agree: false,
  languages: [] as string[],
  contact: "",
});
function onSubmit() {
  console.log(model);
}
</script>
<template>
  <form @submit.prevent="onSubmit">
    <NysTextinput v-model="model.firstName" label="First name" required />
    <NysCheckbox v-model="model.agree" label="I agree to the terms" />
    <NysCheckboxgroup v-model="model.languages" label="Languages">
      <NysCheckbox label="English" value="en" />
      <NysCheckbox label="Spanish" value="es" />
    </NysCheckboxgroup>
    <NysRadiogroup v-model="model.contact" label="Preferred contact" name="contact">
      <NysRadiobutton label="Email" name="contact" value="email" />
      <NysRadiobutton label="Phone" name="contact" value="phone" />
    </NysRadiogroup>
    <NysButton type="submit" label="Submit" />
  </form>
</template>

Bind checkbox and radio groups on NysCheckboxgroup and NysRadiogroup, not on each child. The model updates on the component's nys-input and nys-change events. Add .lazy (v-model.lazy) to update on nys-change alone, as with a native input.

File input has no v-model. Listen for the change event and read the files from its detail:

Copy Code
<NysFileinput
  label="Resume"
  @nys-change="(e) => (model.resume = e.detail.files[0]?.name ?? '')"
/>

Submit and Validate

NYSDS form components are form-associated custom elements. They submit with a plain <form> like native inputs, and required, pattern, and the rest drive the component's own validation and error display.

Copy Code
<form @submit.prevent="onSubmit">
  <NysTextinput v-model="model.firstName" label="First name" required />
  <NysButton type="submit" label="Submit" />
</form>

A NysButton with type="submit" submits through form.requestSubmit(), so an invalid field blocks @submit and the component shows its own error.

To drive errors from your own validation library instead, set the component's error props directly:

Copy Code
<NysTextinput
  v-model="model.firstName"
  label="First name"
  :show-error="!!errors.firstName"
  :error-message="errors.firstName"
/>

Resetting forms: setting the model back to its initial values resets every bound control. The file input has no bound value, so also call reset() on the form itself:

Copy Code
const form = ref<HTMLFormElement | null>(null);
function reset() {
  Object.assign(model, initialModel());
  form.value?.reset();
}

What's next

Explore the full component library on the official NYSDS reference site.