Form controls

DÁP design system components can be used to create forms and input fields in web applications. The form controls are designed to be accessible and easy to use.

Look and feel

Every form control in the DÁP design system has a consistent look and feel. The form controls are designed to be accessible and easy to use, with clear labels and error messages. Every form control has a label, description, tooltip, feedback message, feedback type, and status.

<dap-ds-stack>
<dap-ds-input
  label="Label"
  description="Description"
  feedback="Feedback"
  status="error"
  feedbacktype="negative"
  tooltip="Tooltip"
  placeholder="Enter your first name">
</dap-ds-input>
<dap-ds-checkbox
  label="Label"
  checked
  description="Description"
  feedback="Feedback"
  feedbacktype="positive">
</dap-ds-checkbox>
</dap-ds-stack>
Validation

The form controls in the DÁP design system support HTML5 form validation. You can use the HTML5 form validation attributes like required, min, max, pattern, etc. to validate the form controls. Form components does't have automatic error message generation, you have to set the feedback message manually. Let's see some examples how to use the form controls in your application, in specific frameworks:

Please read the documentation of the specific framework to see how to use the DÁP design system components in your application.

VanillaJs

Every form component tries to use the native HTML5 form validation attributes. You can use the validity property to check the validity of the form control. Read more about the ValidityState interface and the ElementInternals attachInternals functionality.

<form id="input-form">
  <dap-ds-input
    id="text-input"
    name="text-input"
    required
    label="Kotelezo"
    description="Description"
    helperText="Validate for required"
    tooltip="tooltip textecske"
    feedbackType="error">
  </dap-ds-input>
</form>
const inputForm = document.getElementById('input-form')
const textInput = document.getElementById('text-input')

inputForm.addEventListener('submit', (e) => {
  e.preventDefault();

  const showError = () => {
    textInput.status = 'error'
    textInput.setAttribute('feedback', 'Error!!!')
    testInput.setAttribute('feedbackType', 'error')
  }

  if (!textInput.validity.valid) {
    showError()
  }

  const formData = new FormData(inputForm)
  console.log(
    'text-input',
    formData.get('text-input'),
    textInput.validity,
  )
});

React

Import the component from the specific React package of the library. All components have the equivalent React component in the dap-design-system/dist/react package. All component names are suffixed with React.

import React from 'react';
import { DapDSInputReact } from 'dap-design-system/dist/react'
import { Controller, useForm } from 'react-hook-form';

function App() {
  const {
    control,
    handleSubmit,
    setValue,
    formState: { errors },
  } = useForm()

  const onSubmit = (data: any) => {
    console.log('formdata', data)
  }

  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)} noValidate>
        <Controller
          name="input"
          control={control}
          render={({ field: { value } }) => (
            <DapDSInputReact
              id="texInput"
              label="First Name"
              description="Your given name"
              required
              name="textInput"
              value={value}
              feedback={errors?.input?.message?.toString()}
              feedbackType='error'
              onDdsChange={(e) => {
                console.log(e)
                setValue('input', e.detail.value, { shouldValidate: true })}
              }
            >
            </DapDSInputReact>
          )}
          rules={{
            validate: {
              required: value => {
                if (!value) return '*Required'
              },
            },
          }}
        />
      </form>
    </div>
  )
}

React 19

React 19 is the first version of React which support custom elements. You can use the DAP design system components in React 19 without any wrapper component. The only thing you have to be aware of is the event handler names. You have to use the ondds- prefix for the event handlers.

import React from 'react';
import { Controller, useForm } from 'react-hook-form';

function App() {
  const {
    control,
    handleSubmit,
    setValue,
    formState: { errors },
  } = useForm()

  const onSubmit = (data: any) => {
    console.log('formdata', data)
  }

  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)} noValidate>
        <Controller
          name="input"
          control={control}
          render={({ field: { value } }) => (
            <dap-ds-input
              id="texInput"
              label="First Name"
              description="Your given name"
              required
              name="textInput"
              value={value}
              feedback={errors?.input?.message?.toString()}
              feedbackType='error'
              ondds-change={(e) => {
                console.log(e)
                setValue('input', e.detail.value, { shouldValidate: true })}
              }
            >
            </dap-ds-input>
          )}
          rules={{
            validate: {
              required: value => {
                if (!value) return '*Required'
              },
            },
          }}
        />
      </form>
    </div>
  )
}

Next

Next.js solution is very similar to the React solution. You can use the dynamic function to import the component from the specific React package of the library. All components have the equivalent React component in the dap-design-system/dist/react package.

'use client'

import React from 'react';
import { Controller, useForm } from 'react-hook-form';

const DapDSInput = dynamic(
  () => import('dap-design-system/dist/react').then(mod => mod.DapDSInputReact),
  { ssr: false },
)

function App() {
  const {
    control,
    handleSubmit,
    setValue,
    formState: { errors },
  } = useForm()

  const onSubmit = (data: any) => {
    console.log('formdata', data)
  }

  return (
    <div className="App">
      <form onSubmit={handleSubmit(onSubmit)} noValidate>
        <Controller
          name="input"
          control={control}
          render={({ field: { value } }) => (
            <DapDSInput
              id="texInput"
              label="First Name"
              description="Your given name"
              required
              name="textInput"
              value={value}
              feedback={errors?.input?.message?.toString()}
              feedbackType='error'
              onDdsChange={(e) => {
                console.log(e)
                setValue('input', e.detail.value, { shouldValidate: true })}
              }
            >
            </DapDSInput>
          )}
          rules={{
            validate: {
              required: value => {
                if (!value) return '*Required'
              },
            },
          }}
        />
      </form>
    </div>
  )
}

Angular

The following example was tested in Angular 19.

Please note:

  • These examples are basic and just a starting point.
  • If you need more event handlers check out the DAP component docs and the Angular docs.
Directive

Create a directive for the input type you'd like to use in your form:

import {
  Directive,
  ElementRef,
  forwardRef,
  HostListener,
  Renderer2,
} from '@angular/core';
import {
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
} from '@angular/forms';

@Directive({
  selector: 'dap-ds-input[formControlName], dap-ds-input[formControl], dap-ds-input[ngModel]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DapDSInputValueAccessorDirective),
      multi: true,
    },
  ],
})
export class DapDSInputValueAccessorDirective implements ControlValueAccessor {
  private onChange = (value: any) => {};
  private onTouched = () => {};

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  // Write a value to the custom element
  writeValue(value: any): void {
    this.renderer.setProperty(this.el.nativeElement, 'value', value);
  }

  // Register a callback for changes
  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  // Register a callback for touch events
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  // Handle the input event
  @HostListener('dds-change', ['$event.target.value'])
  handleInput(value: any): void {
    this.onChange(value);
  }

  // Handle blur event
  @HostListener('blur')
  handleBlur(): void {
    this.onTouched();
  }
}
Reactive Form

Create the HTML part of the component:

<h2>Reactive form</h2>
<form [formGroup]="myForm">
    <dap-ds-stack>
        <dap-ds-input
            formControlName="firstName"
            required
            label="First Name:"
        ></dap-ds-input>
        <dap-ds-input
            formControlName="lastName"
            required
            label="Last Name:"
        ></dap-ds-input>
        <dap-ds-input
            formControlName="email"
            required
            label="Email:"
        ></dap-ds-input>
    </dap-ds-stack>

    <dap-ds-button (click)="onSubmit()">Submit</dap-ds-button>
</form>

Add the TypeScript component part:

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { DapDSInputValueAccessorDirective } from '../my-input/dap-ds-input.directive';

@Component({
  selector: 'app-reactive',
  imports: [ReactiveFormsModule, DapDSInputValueAccessorDirective],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  templateUrl: './reactive.component.html',
  styleUrl: './reactive.component.scss'
})
export class ReactiveComponent {
  myForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.myForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]],
    });
  }

  onSubmit() {
    if (this.myForm.valid) {
      console.log(this.myForm.value);
    }
  }
}
Template Driven Form

This is the HTML part:

<h2>Template driven form</h2>

<form #form="ngForm">
    <dap-ds-stack>
        <dap-ds-input
            [(ngModel)]="formData.firstName"
            required
            label="First Name:"
            name="firstName"
        ></dap-ds-input>
        <dap-ds-input
            [(ngModel)]="formData.lastName"
            required
            label="Last Name:"
            name="lastName"
        ></dap-ds-input>
        <dap-ds-input
        [(ngModel)]="formData.email"
            required
            label="Email:"
            name="email"
        ></dap-ds-input>
    </dap-ds-stack>

    <dap-ds-button (click)="onSubmit()">Submit</dap-ds-button>
</form>

Add these for the TS component:

import { Component } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { DapDSInputValueAccessorDirective } from '../my-input/dap-ds-input.directive';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-template-driven',
  imports: [DapDSInputValueAccessorDirective, FormsModule],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  templateUrl: './template-driven.component.html',
  styleUrl: './template-driven.component.scss'
})
export class TemplateDrivenComponent {
  formData = {
    firstName: '',
    lastName: '',
    email: '',
  };

  onSubmit() {
    console.log('Form Data:', this.formData);
  }
}

Vue

The following example was tested in Vue 3.5.12.

Create the following component for a basic form functionality:

<template>
  <main>
    <h2>Form</h2>
    <form @submit.prevent="handleSubmit">
      <h2>Sign Up Form</h2>
      <dap-ds-stack>
        <dap-ds-input
          @dds-change="firstNameChange"
          v-model="formData.firstName"
          placeholder="First Name"
        ></dap-ds-input>
        <dap-ds-input v-model="formData.lastName" placeholder="Last Name"></dap-ds-input>
        <dap-ds-input v-model="formData.email" placeholder="Email"></dap-ds-input>
        <dap-ds-button @click="handleSubmit()">Submit</dap-ds-button>
      </dap-ds-stack>
    </form>
  </main>
</template>

<script lang="ts">
export default {
  data() {
    return {
      formData: {
        firstName: '',
        lastName: '',
        email: '',
      },
    }
  },
  methods: {
    handleSubmit() {
      console.log('Form Data:', this.formData)
    },
    firstNameChange() {
      // This runs when focus changed after typing
      console.log('First name has been changed!')
    },
  },
}
</script>

<style scoped></style>