DEV Community

Neweraofcoding
Neweraofcoding

Posted on

Building Custom UI Components from Scratch: Popover, Button, Inputs, Dropdowns, Tabs & More

Modern apps are built on reusable UI components. Whether you’re working in React, Angular, Vue, or plain HTML/CSS/JS, the core idea stays the same:

✅ Build small, accessible, reusable components
✅ Make them consistent (styles + behavior)
✅ Support validation, errors, and states
✅ Keep them composable (work well together)

In this blog, we’ll cover how to create custom UI components like:

  • popover
  • button
  • radio-button, checkbox
  • text-input, date-input
  • dropdown, dropdown-option
  • label, error, note
  • link, message
  • checkbox-group
  • accordion
  • toggle, toggle-group
  • tabs, tab, tab-panel

This guide focuses on:
🎯 structure + behavior + accessibility + styling patterns
(You can implement these in any framework easily.)


1) UI Component Design Rules (Before Writing Code)

✅ 1.1 Component API (Props / Inputs)

Every component should support:

  • id
  • name
  • value
  • disabled
  • required
  • readonly
  • aria-* support
  • className / customClass
  • onChange / onClick / onBlur

✅ 1.2 Component States

Make sure every component supports:

  • Default
  • Hover
  • Focus (keyboard focus visible)
  • Active/Pressed
  • Disabled
  • Error
  • Loading (if applicable)

✅ 1.3 Accessibility (Non-Negotiable)

Follow these rules:

  • Use correct semantic tags (button, input, label)
  • Always connect label with input using for and id
  • Use aria-invalid and aria-describedby for errors
  • Keyboard navigation must work
  • Focus outline must be visible

2) Button Component

What it does

A button triggers actions like submit, open modal, save form, etc.

Key Features

✅ Variants (primary/secondary/ghost)
✅ Sizes (sm/md/lg)
✅ Loading state
✅ Disabled state
✅ Accessible click handling

Example Structure

<button class="btn btn-primary" type="button">
  Save
</button>
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use <button> not <div>
  • Always specify type="button" unless it’s a form submit button
  • Support disabled

3) Text Input Component

What it does

Captures user text: name, email, password, etc.

Structure

<label for="email">Email</label>
<input id="email" type="text" placeholder="Enter email" />
<p class="note">We’ll never share your email.</p>
<p class="error">Email is required.</p>
Enter fullscreen mode Exit fullscreen mode

Validation Support

Use:

  • aria-invalid="true"
  • aria-describedby="email-error"
<input id="email" aria-invalid="true" aria-describedby="email-error" />
<p id="email-error" class="error">Invalid email</p>
Enter fullscreen mode Exit fullscreen mode

4) Date Input Component

What it does

Captures a date value (DOB, booking date, etc.)

Option 1: Native Date Input

<label for="dob">Date of Birth</label>
<input id="dob" type="date" />
Enter fullscreen mode Exit fullscreen mode

Pros:
✅ simple
✅ mobile-friendly
Cons:
❌ inconsistent UI across browsers

Option 2: Custom Date Picker

For consistent UI, build a popover-based calendar (covered in Popover section).


5) Label Component

Labels improve accessibility and usability.

Rules

  • Always clickable
  • Always linked to input using for
<label for="username" class="label">Username</label>
Enter fullscreen mode Exit fullscreen mode

6) Error Component

Errors should be:

  • visible
  • readable
  • accessible
<p class="error" role="alert">Password must be 8 characters</p>
Enter fullscreen mode Exit fullscreen mode

Accessibility Tip

Use role="alert" so screen readers announce it immediately.


7) Note Component

Notes are helpful messages below inputs.

<p class="note">Password must contain a number and symbol.</p>
Enter fullscreen mode Exit fullscreen mode

8) Link Component

A link navigates somewhere.

Use correct semantic tag

<a href="/privacy" class="link">Privacy Policy</a>
Enter fullscreen mode Exit fullscreen mode

For button-like links

If it performs an action, use <button> instead.


9) Message Component (Info / Success / Warning / Error)

Use message banners for system feedback.

<div class="message message-success" role="status">
  Profile updated successfully!
</div>
Enter fullscreen mode Exit fullscreen mode

Types:

  • info
  • success
  • warning
  • error

10) Checkbox Component

Structure

<label class="checkbox">
  <input type="checkbox" />
  Accept terms
</label>
Enter fullscreen mode Exit fullscreen mode

Custom Checkbox Styling Pattern

Hide native checkbox visually but keep accessible.

.checkbox input {
  position: absolute;
  opacity: 0;
}
Enter fullscreen mode Exit fullscreen mode

Then render a custom box.


11) Checkbox Group Component

Checkbox group allows selecting multiple options.

<fieldset>
  <legend>Select skills</legend>

  <label><input type="checkbox" /> Angular</label>
  <label><input type="checkbox" /> React</label>
  <label><input type="checkbox" /> Terraform</label>
</fieldset>
Enter fullscreen mode Exit fullscreen mode

Accessibility

Use <fieldset> + <legend> for grouped controls.


12) Radio Button Component

Radio buttons allow selecting one option from many.

<fieldset>
  <legend>Choose a plan</legend>

  <label><input type="radio" name="plan" value="basic" /> Basic</label>
  <label><input type="radio" name="plan" value="pro" /> Pro</label>
</fieldset>
Enter fullscreen mode Exit fullscreen mode

Rules:

  • Same name = same group
  • Only one can be selected

13) Toggle Component

Toggle is a switch-like checkbox.

Structure

<label class="toggle">
  <input type="checkbox" />
  <span class="toggle-ui"></span>
  Dark mode
</label>
Enter fullscreen mode Exit fullscreen mode

Behavior

  • ON/OFF state
  • keyboard accessible
  • works like checkbox underneath

14) Toggle Group Component

Toggle group works like:

  • segmented control
  • multiple toggles in a row

Single selection (like radio)

Example: “Day / Week / Month”

<div class="toggle-group" role="radiogroup">
  <button role="radio" aria-checked="true">Day</button>
  <button role="radio" aria-checked="false">Week</button>
  <button role="radio" aria-checked="false">Month</button>
</div>
Enter fullscreen mode Exit fullscreen mode

15) Accordion Component

Accordion expands/collapses content.

Structure

<div class="accordion">
  <button class="accordion-trigger" aria-expanded="false">
    What is Terraform?
  </button>

  <div class="accordion-panel" hidden>
    Terraform is Infrastructure as Code...
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Behavior Rules

  • clicking trigger toggles panel
  • use aria-expanded
  • panel uses hidden attribute

16) Tabs Component (tabs, tab, tab-panel)

Tabs allow switching between sections.

Structure

<div class="tabs">
  <div role="tablist" aria-label="Settings Tabs">
    <button role="tab" aria-selected="true" aria-controls="tab-panel-1" id="tab-1">
      Profile
    </button>
    <button role="tab" aria-selected="false" aria-controls="tab-panel-2" id="tab-2">
      Security
    </button>
  </div>

  <div role="tabpanel" id="tab-panel-1" aria-labelledby="tab-1">
    Profile content here...
  </div>

  <div role="tabpanel" id="tab-panel-2" aria-labelledby="tab-2" hidden>
    Security content here...
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Keyboard Support (Important)

Tabs should support:

  • Left/Right arrows to move focus
  • Enter/Space to activate
  • Home/End keys to jump

17) Dropdown Component (dropdown + dropdown-option)

Dropdown is one of the trickiest components.

Option A: Native Select (Recommended)

<label for="country">Country</label>
<select id="country">
  <option>India</option>
  <option>USA</option>
  <option>Canada</option>
</select>
Enter fullscreen mode Exit fullscreen mode

Pros:
✅ fully accessible
✅ mobile friendly
Cons:
❌ limited styling


Option B: Custom Dropdown (Advanced)

Custom dropdown requires:

  • button trigger
  • popover listbox
  • keyboard navigation
  • active option highlight

Basic structure:

<div class="dropdown">
  <button aria-haspopup="listbox" aria-expanded="false">
    Select Country
  </button>

  <ul role="listbox" hidden>
    <li role="option">India</li>
    <li role="option">USA</li>
    <li role="option">Canada</li>
  </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

Keyboard support:

  • Arrow Up/Down to navigate
  • Enter to select
  • Escape to close

18) Popover Component (Most Useful Component)

Popover is a floating panel used for:

  • dropdown menu
  • date picker
  • tooltip-like content
  • action menu

Structure

<div class="popover">
  <button aria-expanded="false">Open</button>

  <div class="popover-content" hidden>
    Popover content here...
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Behavior Rules

✅ Open on click
✅ Close on outside click
✅ Close on Escape
✅ Trap focus if it behaves like a dialog


19) Styling Strategy (Reusable CSS System)

A clean CSS structure:

  • base styles
  • variants
  • utilities

Example class naming:

  • .btn, .btn-primary, .btn-outline
  • .input, .input-error
  • .message-success, .message-error

Example CSS Skeleton

.btn {
  padding: 10px 14px;
  border-radius: 10px;
  cursor: pointer;
  border: 1px solid transparent;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.input {
  padding: 10px;
  border-radius: 10px;
  border: 1px solid #ccc;
}

.input:focus {
  outline: 2px solid #000;
}
Enter fullscreen mode Exit fullscreen mode

20) Recommended Component Build Order

If you're building a design system, follow this order:

  1. Button
  2. Text Input
  3. Label + Error + Note
  4. Checkbox + Radio
  5. Toggle
  6. Message
  7. Popover
  8. Dropdown
  9. Accordion
  10. Tabs
  11. Date Picker

Final Thoughts

Building custom UI components is one of the best ways to level up as a frontend engineer.

Once you master these building blocks, you can create:
✅ consistent UI libraries
✅ reusable design systems
✅ scalable enterprise apps

And your app will look and behave professionally across screens and devices.


Top comments (0)