JavaScript Development Space

Build a ShadCN UI Button Clone Component Using Vanilla JavaScript

Add to your RSS feed11 November 202416 min read
Build a ShadCN UI Button Clone Component Using Vanilla JavaScript

In this tutorial, we'll create a custom button component that replicates the sleek design and functionality of ShadcnUI buttons using only vanilla JavaScript. We'll implement multiple variants, states, and even add a smooth ripple effect – all without any dependencies. This is perfect for projects where you want the polished look of ShadcnUI but need to stick with vanilla JavaScript.

What We'll Create:

  • Multiple button variants (Default, Primary, Secondary, Destructive, Outline, Ghost)
  • Interactive states (Hover, Active, Disabled, Loading)
  • Different rounded borders
  • Sizes of button (sm, md, lg)
  • Transform effect animation
  • Fully responsive design
  • A reusable JavaScript class

Let's dive in and build this component step by step!

We’ll break down this tutorial into three parts:

  1. Part One: We’ll start by creating a button styled to look like a Shadcn button using only HTML and CSS. Then, we’ll connect this button to our page with a simple JavaScript script.
  2. Part Two: In this section, we’ll develop a fully functional button component that can be easily used by adding a CSS class directly within any HTML file.
  3. Part Three: Finally, we’ll create a complete web component. This component will be fully encapsulated and can be integrated simply by adding the <shadcn-button>click me</shadcn-button> tag in your HTML.

To make the article more engaging, we’ll apply slightly different button styles in each section.

Part 1: Create a ShadCN-like button

To create a ShadCN UI-style button clone using pure JavaScript, you'll need to replicate the design and interactivity features, such as hover effects, transitions, and focus states. Here's an example of how you can create a simple button with a ShadCN-inspired style using only JavaScript and CSS:

HTML

html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>ShadCN UI Button Clone</title>
7 <link rel="stylesheet" href="styles.css">
8 </head>
9 <body>
10 <div id="button-container"></div>
11 <script src="script.js"></script>
12 </body>
13 </html>

CSS (styles.css)

css
1 /* Base Button Styling */
2 .shadcn-btn {
3 display: inline-block;
4 padding: 12px 24px;
5 font-size: 16px;
6 font-weight: 600;
7 text-align: center;
8 text-decoration: none;
9 border-radius: 8px;
10 background-color: #4CAF50;
11 color: white;
12 border: 2px solid transparent;
13 outline: none;
14 transition: all 0.3s ease;
15 }
16
17 /* Hover and Focus Effects */
18 .shadcn-btn:hover, .shadcn-btn:focus {
19 background-color: #45a049;
20 transform: translateY(-2px);
21 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
22 }
23
24 /* Active Button Effect */
25 .shadcn-btn:active {
26 background-color: #388e3c;
27 transform: translateY(0);
28 }
29
30 /* Disabled Button Effect */
31 .shadcn-btn:disabled {
32 background-color: #9e9e9e;
33 cursor: not-allowed;
34 box-shadow: none;
35 }

JavaScript (script.js)

js
1 // Creating the button element
2 const buttonContainer = document.getElementById('button-container');
3 const button = document.createElement('button');
4
5 // Adding class and text to the button
6 button.classList.add('shadcn-btn');
7 button.textContent = 'Click Me';
8
9 // Append the button to the container
10 buttonContainer.appendChild(button);
11
12 // Add click event listener (optional functionality)
13 button.addEventListener('click', () => {
14 alert('Button clicked!');
15 });

Explanation:

HTML:

  • The HTML contains a <div> to hold the button. It references external CSS and JavaScript files.

CSS:

  • The .shadcn-btn class styles the button to resemble a ShadCN UI button, with rounded corners, padding, and a green background.
  • The hover, focus, and active states are handled with simple CSS transitions, providing a smooth effect on mouse interaction.
  • Disabled buttons have a distinct gray color and are non-interactive.

JavaScript:

  • JavaScript dynamically creates a <button> element, assigns it the ShadCN button class, and appends it to the DOM.
  • An event listener is added to show an alert when the button is clicked.

This code creates a button with a ShadCN UI-inspired style and functionality, all without relying on a JavaScript framework or library. You can further customize the styles and behaviors by adjusting the CSS and JavaScript as needed.

Part 2: Create a button component

In this part, we'll build a professional-grade button component that mirrors ShadcnUI's functionality and aesthetics using only vanilla JavaScript.

Setting Up the HTML Structure

html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head></head>
4 <body>
5 <div class="button-container">
6 <button class="button button-default">Default Button</button>
7 <button class="button button-primary">Primary Button</button>
8 <button class="button button-destructive">Destructive Button</button>
9 <button class="button button-ghost">Ghost Button</button>
10 <button class="button button-primary" disabled>Disabled Button</button>
11 <button class="button button-primary button-loading">Loading</button>
12 </div>
13 </body>

Styling with CSS

Let's add our styles to create the visual foundation:

css
1 .button-container {
2 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
3 padding: 2rem;
4 display: flex;
5 gap: 1rem;
6 flex-wrap: wrap;
7 }
8
9 .button {
10 display: inline-flex;
11 align-items: center;
12 justify-content: center;
13 border-radius: 0.375rem;
14 font-weight: 500;
15 font-size: 0.875rem;
16 line-height: 1.25rem;
17 padding: 0.5rem 1rem;
18 cursor: pointer;
19 transition: all 0.2s ease;
20 position: relative;
21 user-select: none;
22 }
23
24 /* Button Variants */
25 .button-default {
26 background-color: #ffffff;
27 color: #000000;
28 border: 1px solid #e2e8f0;
29 }
30
31 .button-default:hover {
32 background-color: #f8fafc;
33 border-color: #cbd5e1;
34 }
35
36 .button-primary {
37 background-color: #18181b;
38 color: #ffffff;
39 border: 1px solid #18181b;
40 }
41
42 .button-primary:hover {
43 background-color: #27272a;
44 }
45
46 .button-destructive {
47 background-color: #ef4444;
48 color: #ffffff;
49 border: 1px solid #ef4444;
50 }
51
52 .button-destructive:hover {
53 background-color: #dc2626;
54 }
55
56 .button-ghost {
57 background-color: transparent;
58 border: none;
59 color: #000000;
60 }
61
62 .button-ghost:hover {
63 background-color: #f1f5f9;
64 }

Adding States and Animations

Now let's add styles for different states and the loading animation:

css
1 /* Disabled state */
2 .button:disabled {
3 opacity: 0.5;
4 cursor: not-allowed;
5 pointer-events: none;
6 }
7
8 /* Loading state */
9 .button-loading {
10 position: relative;
11 cursor: wait;
12 }
13
14 .button-loading::after {
15 content: '';
16 position: absolute;
17 width: 1rem;
18 height: 1rem;
19 border: 2px solid transparent;
20 border-top-color: currentColor;
21 border-right-color: currentColor;
22 border-radius: 50%;
23 animation: button-spin 0.6s linear infinite;
24 margin-left: 0.5rem;
25 }
26
27 @keyframes button-spin {
28 from {
29 transform: rotate(0deg);
30 }
31 to {
32 transform: rotate(360deg);
33 }
34 }

JavaScript Implementation

Let's create our button class to handle the functionality:

javascript
1 class ShadcnButton {
2 constructor(element, options = {}) {
3 this.element = element;
4 this.options = {
5 variant: options.variant || 'default',
6 loading: options.loading || false,
7 disabled: options.disabled || false,
8 onClick: options.onClick || null
9 };
10
11 this.init();
12 }
13
14 init() {
15 // Add base class
16 this.element.classList.add('button');
17
18 // Add variant class
19 this.element.classList.add(`button-${this.options.variant}`);
20
21 // Set loading state
22 if (this.options.loading) {
23 this.setLoading(true);
24 }
25
26 // Set disabled state
27 if (this.options.disabled) {
28 this.setDisabled(true);
29 }
30
31 // Add click handler
32 if (this.options.onClick) {
33 this.element.addEventListener('click', this.options.onClick);
34 }
35
36 // Add ripple effect
37 this.element.addEventListener('click', (e) => this.createRipple(e));
38 }
39
40 setLoading(loading) {
41 if (loading) {
42 this.element.classList.add('button-loading');
43 this.element.disabled = true;
44 } else {
45 this.element.classList.remove('button-loading');
46 this.element.disabled = this.options.disabled;
47 }
48 }
49
50 setDisabled(disabled) {
51 this.element.disabled = disabled;
52 this.options.disabled = disabled;
53 }
54 }

Adding the Ripple Effect

Let's implement the ripple animation:

javascript
1 createRipple(event) {
2 const button = event.currentTarget;
3 const ripple = document.createElement('span');
4
5 const diameter = Math.max(button.clientWidth, button.clientHeight);
6 const radius = diameter / 2;
7
8 ripple.style.width = ripple.style.height = `${diameter}px`;
9 ripple.style.left = `${event.clientX - button.offsetLeft - radius}px`;
10 ripple.style.top = `${event.clientY - button.offsetTop - radius}px`;
11 ripple.style.position = 'absolute';
12 ripple.style.borderRadius = '50%';
13 ripple.style.transform = 'scale(0)';
14 ripple.style.animation = 'ripple 0.6s linear';
15 ripple.style.backgroundColor = 'rgba(255, 255, 255, 0.7)';
16
17 const style = document.createElement('style');
18 style.textContent = `
19 @keyframes ripple {
20 to {
21 transform: scale(4);
22 opacity: 0;
23 }
24 }
25 `;
26
27 document.head.appendChild(style);
28 button.appendChild(ripple);
29
30 setTimeout(() => {
31 ripple.remove();
32 style.remove();
33 }, 600);
34 }

Initialization

Finally, let's initialize our buttons:

javascript
1 // Initialize all buttons on the page
2 document.querySelectorAll('.button').forEach(button => {
3 new ShadcnButton(button, {
4 variant: button.classList.contains('button-primary') ? 'primary' :
5 button.classList.contains('button-destructive') ? 'destructive' :
6 button.classList.contains('button-ghost') ? 'ghost' : 'default',
7 loading: button.classList.contains('button-loading'),
8 disabled: button.disabled,
9 onClick: (e) => {
10 console.log('Button clicked:', e.target.textContent);
11 }
12 });
13 });

Complete Solution

Check the full implementation in action:

Part 3: Building a Complete Component

Let's start by setting up the structure of our application. We'll create three files: index.html, style.css, and app.js, and fill each with basic markup to get started.

HTML

html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>JS Component</title>
7 <link rel="stylesheet" href="style.css">
8 </head>
9 <body>
10 <div class="container">
11 </div>
12 <script src="app.js"></script>
13 </body>
14 </html>

CSS

css
1 * {
2 box-sizing: border-box;
3 padding: 0;
4 margin: 0;
5 }
6 .container {
7 margin-top: 200px;
8 }
9 .wrapper {
10 display: flex;
11 align-items: center;
12 justify-content: center;
13 gap: 24px;
14 margin-top: 24px;
15 }

Together, these styles create a clean and centered layout for the page content, with evenly spaced items within the wrapper.

JavaScript

Component Basics

js
1 class Component extends HTMLElement {
2 constructor() {
3 super();
4 this.init(); // Calls init() to initialize shadow DOM and state
5 }
6
7 // Initialize the shadow DOM and set up the state object
8 init() {
9 const shadow = this.attachShadow({ mode: 'open' });
10 this.state = {}; // Initializes an empty state object
11 }
12 }

The Component class extends HTMLElement to create a foundation for our custom elements. It initializes an "open" shadow DOM, which encapsulates styles and structure, preventing them from affecting the main document.

The Element.attachShadow() method in JavaScript is used to attach a shadow DOM to an element, which enables encapsulation of HTML, CSS, and JavaScript. This feature is key to building Web Components and helps avoid style and script conflicts between components.

Benefits of attachShadow()

  • Encapsulation: Styles and structure inside the shadow DOM are isolated from the rest of the document, which prevents unintended style leaks.
  • Reusability: Components can be reused without worrying about conflicts with external styles or scripts.
  • Clean Component Boundaries: The method allows building components with their own isolated styles, markup, and behavior, making them self-contained and modular.

In summary, attachShadow() is crucial for creating robust web components with encapsulation and style isolation, improving the maintainability and scalability of web applications.

Creating the Button Component

js
1 class Button extends Component {
2 constructor() {
3 super();
4 this._container = document.createElement('button');
5 this.data();
6 this._style();
7 this._html();
8 }
9 }

This Button class extends the Component class, allowing it to reuse the shadow DOM initialization. The constructor:

  • Creates the button (this._container).
  • Initializes component data (this.data()).
  • Applies styles and structure (this._style() and this._html()).

Defining Button Styles

The _style method defines CSS for button variants, sizes, and styles, including color, padding, and animations.

js
1 _style() {
2 // Create a <style> element to hold the CSS for the button
3 const buttonCss = document.createElement('style');
4 // Define button background colors, borders, and text colors based on the type
5 const variants = {
6 default: ['#4c97f8', '#5faefb', 'white'],
7 destructive: ['#d6d3d1', '#e7e5e4', 'black'],
8 outline: ['#737373', '#a3a3a3', 'black'],
9 secondary: ['#10b981', '#34d399', 'white'],
10 ghost: ['#f3f4f6', '#f9fafb', 'black'],
11 };
12 const roundeds = {
13 none: '0',
14 sm: '2px',
15 default: '4px',
16 md: '6px',
17 lg: '8px',
18 xl: '12px',
19 '2xl': '16px',
20 '3xl': '24px',
21 full: '9999px'
22 }
23 // Style button according to its variant, roundness, and size
24 buttonCss.textContent = `
25 .btn {
26 display: inline-block;
27 background: ${variants[this.state.variant][0]};
28 border: 2px solid transparent;
29 color: ${variants[this.state.variant][2]};
30 line-height: 1;
31 white-space: nowrap;
32 text-align: center;
33 box-sizing: border-box;
34 padding: 12px 24px;
35 font-size: 16px;
36 font-weight: 600;
37 text-align: center;
38 text-decoration: none;
39 border-radius: ${roundeds[this.state.rounded]};
40 cursor: pointer;
41 outline: none;
42 transition: all 0.3s ease;
43 }
44 .btn:hover {
45 background: ${variants[this.state.variant][1]};
46 transform: translateY(-2px);
47 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
48 }
49 .btn-md {
50 font-size: 14px;
51 padding: 10px 20px;
52 }
53 .btn-sm {
54 font-size: 12px;
55 padding: 9px 15px;
56 }
57 .btn-lg {
58 font-size: 16px;
59 padding: 12px 24px;
60 }
61 .btn:disabled {
62 opacity: 0.4;
63 cursor: not-allowed;
64 pointer-events: none;
65 }
66 .btn-loading {
67 position: relative;
68 cursor: wait;
69 }
70 .btn-loading::after {
71 content: '';
72 position: absolute;
73 width: 1rem;
74 height: 1rem;
75 right: 40%;
76 border: 2px solid transparent;
77 border-top-color: currentColor;
78 border-right-color: currentColor;
79 border-radius: 50%;
80 animation: button-spin 0.6s linear infinite;
81 }
82
83 @keyframes button-spin {
84 from {
85 transform: rotate(0deg);
86 }
87 to {
88 transform: rotate(360deg);
89 }
90 }
91 `;
92 this.shadowRoot.appendChild(buttonCss); // Append styles to shadow DOM
93 }

The buttonCss variable holds styling rules for various button states:

  • Variants: Color schemes for different button types (default, destructive, outline).
  • Rounded Corners: Options for rounded button shapes (none, sm, md, full).
  • Loading Spinner: Displays a spinner when loading.

Defining HTML Structure

The _html method creates the button’s structure and applies classes based on attributes like size and variant.

js
1 // Create the button's HTML structure
2 _html() {
3 this._container.classList.add('btn'); // Apply base button class
4
5 if (this.state.size && this.state.size !== 'default') {
6 this._container.classList.add(`btn-${this.state.size}`); // Add size class if applicable
7 }
8 this._container.innerHTML = `<span><slot></slot></span>`; // Add slot for content projection
9 this.shadowRoot.appendChild(this._container); // Append the button to shadow DOM
10 }

The <slot> element allows custom content to be placed inside the <shadcn-button>, making it flexible and reusable.

Getting Attributes and Handling States

Several getAttr functions retrieve the button's variant, size, rounded, disabled, and loading states from its attributes.

js
1 getAttrVariant() {
2 try {
3 const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost'];
4 let variant = this.getAttribute('variant');
5 if (variant && variants.includes(variant)) {
6 this.state.variant = variant;
7 } else {
8 this.state.variant = 'default'; // Default type if invalid
9 }
10 } catch (e) {
11 this.state.variant = 'default'; // Default type if error
12 }
13 }
14
15 // Get 'round' attribute value and set it in state
16 getAttrRounded() {
17 const rounded = this.getAttribute('rounded');
18 const roundeds = ['none', 'sm', 'default', 'md', 'lg', 'xl', '2xl', '3xl', 'full'];
19 try {
20 if (rounded && roundeds.includes(rounded)) {
21 this.state.rounded = rounded; // Set roundness based on attribute
22 } else {
23 this.state.rounded = 'none'; // Default to none
24 }
25 } catch (e) {
26 this.state.rounded = 'none'; // Default to none if error
27 }
28 }
29
30 // Get 'size' attribute value and set it in state
31 getAttrSize() {
32 const size = this.getAttribute('size');
33 const sizes = ['md', 'sm', 'lg', 'default'];
34 try {
35 if (size && sizes.includes(size)) {
36 this.state.size = size; // Set size based on attribute
37 } else {
38 this.state.size = 'default'; // Default to 'default' size if invalid
39 }
40 } catch (e) {
41 this.state.size = 'default'; // Default size if error
42 }
43 }
44 getAttrDisabled() {
45 const disabled = this.getAttribute('disabled');
46 if (disabled !== null) {
47 if (disabled === 'true') {
48 this.setDisabled(true);
49 } else if (disabled === 'false') {
50 this.setDisabled(false);
51 }
52 }
53 }
54 getAttrLoading() {
55 const loading = this.getAttribute('loading');
56 if (loading === "true") {
57 this._container.classList.add('btn-loading');
58 } else if (loading === "false") {
59 this._container.classList.remove('btn-loading');
60 }
61 }

These functions validate and set the state based on provided attributes. For instance, getAttrDisabled manages the disabled state by adding or removing the disabled attribute on the button element.

To add a setDisabled method, we'll incorporate logic for handling the button's disabled state. This method will check if the button should be disabled and then update the component’s state and actual DOM attributes accordingly.

Here’s how to add setDisabled and how it fits into our overall button component:

Adding setDisabled for Managing Button State

The setDisabled function allows dynamic updates to the button's disabled state. This function checks if the disabled parameter is true or false and then updates the button accordingly.

To add a setDisabled method, we'll incorporate logic for handling the button's disabled state. This method will check if the button should be disabled and then update the component’s state and actual DOM attributes accordingly.

Here’s how to add setDisabled and how it fits into our overall button component:

js
1 setDisabled(disabled) {
2 this.state.disabled = disabled;
3 if (disabled) {
4 this._container.setAttribute('disabled', 'true'); // Adds 'disabled' attribute
5 } else {
6 this._container.removeAttribute('disabled'); // Removes 'disabled' attribute
7 }
8 }

If you want to disable or enable <shadcn-button> dynamically in your app, you could call setDisabled(true) or setDisabled(false) in your JavaScript to control the button's availability based on user interaction, loading states, or other events.

Putting It All Together

Finally, customElements.define registers the <shadcn-button> component.

js
1 customElements.define('shadcn-button', Button);

This allows us to use <shadcn-button></shadcn-button> anywhere in HTML with the functionality, styles, and customizations defined above. By extending HTMLElement and using shadow DOM, this approach keeps our component modular, customizable, and isolated from global styles.

Full JavaScript Code

js
1 // The Component class creates a custom HTML element by extending HTMLElement
2 class Component extends HTMLElement {
3 constructor() {
4 super();
5 this.init(); // Calls init() to initialize shadow DOM and state
6 }
7
8 // Initialize the shadow DOM and set up the state object
9 init() {
10 const shadow = this.attachShadow({
11 mode: 'open', // Creates an open shadow DOM
12 });
13 this.state = {}; // Initializes an empty state object
14 }
15 }
16
17 // <shadcn-button></shadcn-button> is the custom element
18 class Button extends Component {
19 constructor() {
20 super(); // Inherits from Component
21 this._container = document.createElement('button'); // Creates the main button element
22 this.data(); // Initializes data for the button
23 this._style(); // Applies button styles
24 this._html(); // Creates the button's HTML structure
25 }
26
27 // Initialize data such as button type, round, and size
28 data() {
29 this.state = {
30 variant: 'default', // Sets default type for button
31 rounded: 'none', // Determines if button is rounded
32 size: 'default', // Default size of the button
33 disabled: false,
34 loading: false
35 };
36 this.getAttrVariant(); // Get type from attributes
37 this.getAttrRounded(); // Get roundness from attributes
38 this.getAttrSize(); // Get size from attributes
39 this.getAttrDisabled();
40 this.getAttrLoading();
41 }
42
43 // Add styles to the shadow DOM
44 _style() {
45 // Create a <style> element to hold the CSS for the button
46 const buttonCss = document.createElement('style');
47 // Define button background colors, borders, and text colors based on the type
48 const variants = {
49 default: ['#4c97f8', '#5faefb', 'white'],
50 destructive: ['#d6d3d1', '#e7e5e4', 'black'],
51 outline: ['#737373', '#a3a3a3', 'black'],
52 secondary: ['#10b981', '#34d399', 'white'],
53 ghost: ['#f3f4f6', '#f9fafb', 'black'],
54 };
55 const roundeds = {
56 none: '0',
57 sm: '2px',
58 default: '4px',
59 md: '6px',
60 lg: '8px',
61 xl: '12px',
62 '2xl': '16px',
63 '3xl': '24px',
64 full: '9999px'
65 }
66 // Style button according to its variant, roundness, and size
67 buttonCss.textContent = `
68 .btn {
69 display: inline-block;
70 background: ${variants[this.state.variant][0]};
71 border: 2px solid transparent;
72 color: ${variants[this.state.variant][2]};
73 line-height: 1;
74 white-space: nowrap;
75 text-align: center;
76 box-sizing: border-box;
77 padding: 12px 24px;
78 font-size: 16px;
79 font-weight: 600;
80 text-align: center;
81 text-decoration: none;
82 border-radius: ${roundeds[this.state.rounded]};
83 cursor: pointer;
84 outline: none;
85 transition: all 0.3s ease;
86 }
87 .btn:hover {
88 background: ${variants[this.state.variant][1]};
89 transform: translateY(-2px);
90 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
91 }
92 .btn-md {
93 font-size: 14px;
94 padding: 10px 20px;
95 }
96 .btn-sm {
97 font-size: 12px;
98 padding: 9px 15px;
99 }
100 .btn-lg {
101 font-size: 16px;
102 padding: 12px 24px;
103 }
104 .btn:disabled {
105 opacity: 0.4;
106 cursor: not-allowed;
107 pointer-events: none;
108 }
109 .btn-loading {
110 position: relative;
111 cursor: wait;
112 }
113 .btn-loading::after {
114 content: '';
115 position: absolute;
116 width: 1rem;
117 height: 1rem;
118 right: 40%;
119 border: 2px solid transparent;
120 border-top-color: currentColor;
121 border-right-color: currentColor;
122 border-radius: 50%;
123 animation: button-spin 0.6s linear infinite;
124 }
125
126 @keyframes button-spin {
127 from {
128 transform: rotate(0deg);
129 }
130 to {
131 transform: rotate(360deg);
132 }
133 }
134 `;
135 this.shadowRoot.appendChild(buttonCss); // Append styles to shadow DOM
136 }
137
138 // Create the button's HTML structure
139 _html() {
140 this._container.classList.add('btn'); // Apply base button class
141
142 if (this.state.size && this.state.size !== 'default') {
143 this._container.classList.add(`btn-${this.state.size}`); // Add size class if applicable
144 }
145 this._container.innerHTML = `<span><slot></slot></span>`; // Add slot for content projection
146 this.shadowRoot.appendChild(this._container); // Append the button to shadow DOM
147 }
148
149 // Get 'type' attribute value and set it in state
150 getAttrVariant() {
151 try {
152 const variants = ['default', 'destructive', 'outline', 'secondary', 'ghost'];
153 let variant = this.getAttribute('variant');
154 if (variant && variants.includes(variant)) {
155 this.state.variant = variant;
156 } else {
157 this.state.variant = 'default'; // Default type if invalid
158 }
159 } catch (e) {
160 this.state.variant = 'default'; // Default type if error
161 }
162 }
163
164 // Get 'round' attribute value and set it in state
165 getAttrRounded() {
166 const rounded = this.getAttribute('rounded');
167 const roundeds = ['none', 'sm', 'default', 'md', 'lg', 'xl', '2xl', '3xl', 'full'];
168 try {
169 if (rounded && roundeds.includes(rounded)) {
170 this.state.rounded = rounded; // Set roundness based on attribute
171 } else {
172 this.state.rounded = 'none'; // Default to none
173 }
174 } catch (e) {
175 this.state.rounded = 'none'; // Default to none if error
176 }
177 }
178
179 // Get 'size' attribute value and set it in state
180 getAttrSize() {
181 const size = this.getAttribute('size');
182 const sizes = ['md', 'sm', 'lg', 'default'];
183 try {
184 if (size && sizes.includes(size)) {
185 this.state.size = size; // Set size based on attribute
186 } else {
187 this.state.size = 'default'; // Default to 'default' size if invalid
188 }
189 } catch (e) {
190 this.state.size = 'default'; // Default size if error
191 }
192 }
193 getAttrDisabled() {
194 const disabled = this.getAttribute('disabled');
195 if (disabled !== null) {
196 if (disabled === 'true') {
197 this.setDisabled(true);
198 } else if (disabled === 'false') {
199 this.setDisabled(false);
200 }
201 }
202 }
203 getAttrLoading() {
204 const loading = this.getAttribute('loading');
205 if (loading === "true") {
206 this._container.classList.add('btn-loading');
207 } else if (loading === "false") {
208 this._container.classList.remove('btn-loading');
209 }
210 }
211 setDisabled(disabled) {
212 this.state.disabled = disabled;
213 if (disabled) {
214 this._container.setAttribute('disabled', disabled);
215 } else {
216 this._container.removeAttribute('disabled');
217 }
218 }
219 }
220
221 // Define the custom element '<Button>'
222 customElements.define('shadcn-button', Button);

Result:

Conclusion

By using Web Components, this button component can be customized and reused across different applications. With options for variant, size, and rounded properties, this <shadcn-button> element provides a robust and flexible button for any UI, offering reusable, styled, and dynamic components without external libraries.

JavaScript Development Space

© 2024 JavaScript Development Space - Master JS and NodeJS. All rights reserved.