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.
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.
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.
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();
}
}
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);
}
}
}
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>