npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@izan-sanchez/izan-form

v1.0.1

Published

Un componente de formulario web component universal construido con Svelte. Compatible con React, Vue, Angular y JavaScript vanilla.

Readme

@izan-sanchez/izan-form 🚀

Un componente de formulario web moderno y accesible construido con Svelte que funciona en cualquier framework o HTML vanilla. Diseño glassmorphism, validación integrada, y totalmente personalizable.

npm version npm downloads license bundle size

✨ Características

  • 🎨 Diseño Moderno: Estilo glassmorphism con animaciones suaves
  • 🌙 Modo Oscuro/Claro: Soporte nativo para temas
  • Accesible: Cumple con estándares WCAG
  • 📱 Responsive: Optimizado para móviles y desktop
  • 🔧 Flexible: Múltiples tipos de campos soportados
  • Ligero: Sin dependencias externas
  • 🌐 Universal: Funciona en cualquier framework
  • 🔒 Validación: Validación HTML5 integrada
  • 🎭 Animaciones: Transiciones suaves y micro-interacciones
  • 🎯 TypeScript: Incluye definiciones de tipos

📦 Instalación

npm install @izan-sanchez/izan-form
yarn add @izan-sanchez/izan-form
pnpm add @izan-sanchez/izan-form

📋 Propiedades (Attributes)

| Propiedad | Tipo | Valor por defecto | Descripción | |-----------|------|-------------------|-------------| | title | string | '' | Título del formulario | | submittext | string | 'Enviar' | Texto del botón de envío | | loadingtext | string | 'Enviando...' | Texto mostrado durante el envío | | theme | string | 'light' | Tema del formulario ('light' | 'dark') | | fields | string | array | '[]' | Configuración de campos (JSON string o array) | | privacypolicyurl | string | '' | URL de la política de privacidad |

🔧 Configuración de Campos

Tipos de Campo Soportados

Text Input

{
    name: 'firstName',
    type: 'text',
    label: 'Nombre',
    required: true,
    minlength: 2,
    maxlength: 50,
}

Email

{
    name: 'email',
    type: 'email',
    label: 'Correo electrónico',
    required: true,
}

Password

{
    name: 'password',
    type: 'password',
    label: 'Contraseña',
    required: true,
    minlength: 8,
    maxlength: 128,}

Number

{
    name: 'age',
    type: 'number',
    label: 'Edad',
    required: true,
    min: 18,
    max: 120,
}

Tel

{
    name: 'phone',
    type: 'tel',
    label: 'Teléfono',
}

URL

{
    name: 'website',
    type: 'url',
    label: 'Sitio web',
}

Date

{
    name: 'birthdate',
    type: 'date',
    label: 'Fecha de nacimiento',
    required: true,
    min: '1900-01-01',
    max: '2024-12-31'
}

Time

{
    name: 'appointment',
    type: 'time',
    label: 'Hora de cita',
    required: true,
    min: '09:00',
    max: '18:00'
}

Textarea

{
    name: 'message',
    type: 'textarea',
    label: 'Mensaje',
    required: true,
    rows: 5,
    minlength: 10,
    maxlength: 1000,
}

Select

{
    name: 'country',
    type: 'select',
    label: 'País',
    required: true,
    options: [
        { value: 'es', label: 'España' },
        { value: 'mx', label: 'México' },
        { value: 'ar', label: 'Argentina' },
        { value: 'co', label: 'Colombia', selected: true }
    ],
}

Multiple Select

{
    name: 'skills',
    type: 'select',
    label: 'Habilidades',
    multiple: true,
    options: [
        { value: 'js', label: 'JavaScript' },
        { value: 'react', label: 'React' },
        { value: 'vue', label: 'Vue.js' },
        { value: 'svelte', label: 'Svelte' }
    ]
}

Radio Buttons

{
    name: 'plan',
    type: 'radio',
    label: 'Plan de suscripción',
    required: true,
    options: [
        { value: 'basic', label: 'Básico - €9/mes' },
        { value: 'pro', label: 'Pro - €19/mes', selected: true },
        { value: 'premium', label: 'Premium - €39/mes' }
    ]
}

Checkbox

{
    name: 'newsletter',
    type: 'checkbox',
    label: 'Suscribirme al newsletter',
}

Privacy Checkbox (especial)

{
    name: 'privacy',
    type: 'checkbox',
    label: 'Acepto la política de privacidad',
    required: true
}

Propiedades Comunes de Campos

| Propiedad | Tipo | Descripción | |-----------|------|-------------| | name | string | Requerido. Nombre único del campo | | type | string | Requerido. Tipo de campo | | label | string | Requerido. Etiqueta visible del campo | | required | boolean | Si el campo es obligatorio |

Propiedades Específicas por Tipo

Text, Email, Password, Tel, URL

  • minlength: Longitud mínima
  • maxlength: Longitud máxima

Number, Date, Time

  • min: Valor mínimo
  • max: Valor máximo

Textarea

  • rows: Número de filas
  • minlength: Longitud mínima
  • maxlength: Longitud máxima

Select

  • multiple: Permitir selección múltiple
  • options: Array de opciones con value, label y selected

Radio

  • options: Array de opciones con value, label y selected

🎯 Uso Básico

HTML Vanilla

<!DOCTYPE html>
<html>
<head>
    <script type="module" src="node_modules/@izan-sanchez/izan-form/dist/izan-form.js"></script>
</head>
<body>
    <izan-form
        title="Formulario de Contacto"
        submittext="Enviar Mensaje"
        theme="light"
        fields='[
            {
                "name": "name",
                "type": "text",
                "label": "Nombre",
                "required": true
            },
            {
                "name": "email",
                "type": "email",
                "label": "Email",
                "required": true
            },
            {
                "name": "message",
                "type": "textarea",
                "label": "Mensaje",
                "required": true
            }
        ]'>
    </izan-form>

    <script>
        document.querySelector('izan-form').addEventListener('formsubmit', (event) => {
            console.log('Datos:', event.detail.values);
            // Procesar datos aquí
        });
    </script>
</body>
</html>

React

import React, { useEffect, useRef } from 'react';
import '@izan-sanchez/izan-form';

function ContactForm() {
    const formRef = useRef(null);

    const fields = [
        {
            name: 'name',
            type: 'text',
            label: 'Nombre',
            required: true,
            placeholder: 'Tu nombre completo'
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true,
            placeholder: '[email protected]'
        },
        {
            name: 'phone',
            type: 'tel',
            label: 'Teléfono',
            placeholder: '+34 600 000 000'
        },
        {
            name: 'message',
            type: 'textarea',
            label: 'Mensaje',
            required: true,
            rows: 5,
            placeholder: 'Escribe tu mensaje aquí...'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto la política de privacidad',
            required: true
        }
    ];

    useEffect(() => {
        const form = formRef.current;
        if (form) {
            const handleSubmit = (event) => {
                console.log('Form data:', event.detail.values);
                // Aquí puedes enviar los datos a tu API
                fetch('/api/contact', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(event.detail.values)
                });
            };

            const handleReady = () => {
                console.log('Form is ready');
            };

            form.addEventListener('formsubmit', handleSubmit);
            form.addEventListener('ready', handleReady);
            
            return () => {
                form.removeEventListener('formsubmit', handleSubmit);
                form.removeEventListener('ready', handleReady);
            };
        }
    }, []);

    return (
        <izan-form
            ref={formRef}
            title="Formulario de Contacto"
            submittext="Enviar Mensaje"
            loadingtext="Enviando..."
            theme="light"
            fields={JSON.stringify(fields)}
            successmessage="¡Mensaje enviado correctamente!"
            errormessage="Error al enviar el mensaje. Inténtalo de nuevo."
            privacypolicyurl="https://tu-sitio.com/privacy"
            autocomplete="on"
        />
    );
}

export default ContactForm;

Vue 3

<template>
    <izan-form
        ref="formComponent"
        :title="formConfig.title"
        :submittext="formConfig.submitText"
        :theme="currentTheme"
        :fields="JSON.stringify(formConfig.fields)"
        :successmessage="formConfig.successMessage"
        :errormessage="formConfig.errorMessage"
        :privacypolicyurl="formConfig.privacyUrl"
        @formsubmit="handleFormSubmit"
        @ready="onFormReady"
    />
    
    <div class="form-controls">
        <button @click="toggleTheme">
            Cambiar a tema {{ currentTheme === 'light' ? 'oscuro' : 'claro' }}
        </button>
        <button @click="resetForm">Resetear formulario</button>
        <button @click="validateForm">Validar formulario</button>
    </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import '@izan-sanchez/izan-form';

const formComponent = ref(null);
const currentTheme = ref('light');

const formConfig = reactive({
    title: 'Registro de Usuario',
    submitText: 'Crear Cuenta',
    successMessage: '¡Cuenta creada exitosamente!',
    errorMessage: 'Error al crear la cuenta',
    privacyUrl: 'https://tu-sitio.com/privacy',
    fields: [
        {
            name: 'firstName',
            type: 'text',
            label: 'Nombre',
            required: true,
            minlength: 2
        },
        {
            name: 'lastName',
            type: 'text',
            label: 'Apellidos',
            required: true,
            minlength: 2
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true
        },
        {
            name: 'password',
            type: 'password',
            label: 'Contraseña',
            required: true,
            minlength: 8
        },
        {
            name: 'country',
            type: 'select',
            label: 'País',
            required: true,
            options: [
                { value: 'es', label: 'España' },
                { value: 'mx', label: 'México' },
                { value: 'ar', label: 'Argentina' },
                { value: 'co', label: 'Colombia' }
            ]
        },
        {
            name: 'gender',
            type: 'radio',
            label: 'Género',
            options: [
                { value: 'male', label: 'Masculino' },
                { value: 'female', label: 'Femenino' },
                { value: 'other', label: 'Otro' },
                { value: 'prefer-not-to-say', label: 'Prefiero no decir' }
            ]
        },
        {
            name: 'newsletter',
            type: 'checkbox',
            label: 'Suscribirme al newsletter'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto la política de privacidad',
            required: true
        }
    ]
});

const handleFormSubmit = async (event) => {
    console.log('Form submitted:', event.detail.values);
    
    try {
        const response = await fetch('/api/register', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(event.detail.values)
        });
        
        if (!response.ok) {
            throw new Error('Registration failed');
        }
        
        const result = await response.json();
        console.log('Registration successful:', result);
    } catch (error) {
        console.error('Registration error:', error);
    }
};

const onFormReady = () => {
    console.log('Form component is ready');
};

const toggleTheme = () => {
    currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
};

const resetForm = () => {
    if (formComponent.value) {
        formComponent.value.reset();
    }
};

const validateForm = () => {
    if (formComponent.value) {
        const isValid = formComponent.value.validate();
        console.log('Form is valid:', isValid);
    }
};
</script>

<style scoped>
.form-controls {
    margin-top: 20px;
    display: flex;
    gap: 10px;
}

.form-controls button {
    padding: 8px 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
}
</style>

Angular

// app.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import '@izan-sanchez/izan-form';

@Component({
  selector: 'app-contact',
  template: `
    <izan-form
      #formElement
      [attr.title]="formTitle"
      [attr.submittext]="submitText"
      [attr.theme]="theme"
      [attr.fields]="fieldsJson"
      [attr.successmessage]="successMessage"
      [attr.errormessage]="errorMessage"
      [attr.privacypolicyurl]="privacyUrl"
    ></izan-form>
    
    <div class="controls">
      <button (click)="toggleTheme()">Cambiar Tema</button>
      <button (click)="resetForm()">Reset</button>
      <button (click)="getFormValues()">Ver Valores</button>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('formElement') formElement!: ElementRef;

  formTitle = 'Formulario de Contacto';
  submitText = 'Enviar';
  theme = 'light';
  successMessage = '¡Formulario enviado correctamente!';
  errorMessage = 'Error al enviar el formulario';
  privacyUrl = 'https://tu-sitio.com/privacy';

  fields = [
    {
      name: 'company',
      type: 'text',
      label: 'Empresa',
      required: true
    },
    {
      name: 'contact_name',
      type: 'text',
      label: 'Nombre de contacto',
      required: true
    },
    {
      name: 'email',
      type: 'email',
      label: 'Email corporativo',
      required: true
    },
    {
      name: 'budget',
      type: 'select',
      label: 'Presupuesto',
      required: true,
      options: [
        { value: '1000-5000', label: '1.000€ - 5.000€' },
        { value: '5000-10000', label: '5.000€ - 10.000€' },
        { value: '10000-25000', label: '10.000€ - 25.000€' },
        { value: '25000+', label: 'Más de 25.000€' }
      ]
    },
    {
      name: 'services',
      type: 'checkbox',
      label: 'Desarrollo web'
    },
    {
      name: 'privacy',
      type: 'checkbox',
      label: 'Acepto la política de privacidad',
      required: true
    }
  ];

  get fieldsJson(): string {
    return JSON.stringify(this.fields);
  }

  ngAfterViewInit() {
    const form = this.formElement.nativeElement;
    
    form.addEventListener('formsubmit', (event: any) => {
      console.log('Form submitted:', event.detail.values);
      this.handleFormSubmit(event.detail.values);
    });

    form.addEventListener('ready', () => {
      console.log('Form is ready');
    });
  }

  handleFormSubmit(values: any) {
    // Procesar los datos del formulario
    console.log('Processing form data:', values);
  }

  toggleTheme() {
    this.theme = this.theme === 'light' ? 'dark' : 'light';
  }

  resetForm() {
    const form = this.formElement.nativeElement;
    if (form && form.reset) {
      form.reset();
    }
  }

  getFormValues() {
    const form = this.formElement.nativeElement;
    if (form && form.getValues) {
      const values = form.getValues();
      console.log('Current form values:', values);
    }
  }
}

Astro

---
// pages/contact.astro
const fields = [
  {
    name: 'name',
    type: 'text',
    label: 'Nombre',
    required: true
  },
  {
    name: 'email',
    type: 'email',
    label: 'Email',
    required: true
  },
  {
    name: 'subject',
    type: 'select',
    label: 'Asunto',
    required: true,
    options: [
      { value: 'general', label: 'Consulta general' },
      { value: 'support', label: 'Soporte técnico' },
      { value: 'sales', label: 'Ventas' },
      { value: 'partnership', label: 'Colaboración' }
    ]
  },
  {
    name: 'priority',
    type: 'radio',
    label: 'Prioridad',
    options: [
      { value: 'low', label: 'Baja' },
      { value: 'medium', label: 'Media', selected: true },
      { value: 'high', label: 'Alta' }
    ]
  },
  {
    name: 'message',
    type: 'textarea',
    label: 'Mensaje',
    required: true,
    rows: 6
  },
  {
    name: 'privacy',
    type: 'checkbox',
    label: 'Acepto la política de privacidad',
    required: true
  }
];
---

<html lang="es">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Contacto - Mi Sitio</title>
</head>
<body>
    <main>
        <h1>Contacto</h1>
        
        <izan-form
            id="contact-form"
            title="Envíanos un mensaje"
            submittext="Enviar mensaje"
            loadingtext="Enviando..."
            theme="light"
            fields={JSON.stringify(fields)}
            successmessage="¡Mensaje enviado correctamente!"
            errormessage="Error al enviar el mensaje"
            privacypolicyurl="/privacy"
            action="/api/contact"
            method="POST"
            autocomplete="on"
        />
    </main>

    <script>
        import '@izan-sanchez/izan-form';
        
        document.addEventListener('DOMContentLoaded', () => {
            const form = document.getElementById('contact-form');
            
            if (form) {
                form.addEventListener('formsubmit', async (event) => {
                    console.log('Form data:', event.detail.values);
                    
                    // Ejemplo de envío a API
                    try {
                        const response = await fetch('/api/contact', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            },
                            body: JSON.stringify(event.detail.values)
                        });
                        
                        if (response.ok) {
                            console.log('Message sent successfully');
                        } else {
                            console.error('Failed to send message');
                        }
                    } catch (error) {
                        console.error('Error:', error);
                    }
                });

                form.addEventListener('ready', () => {
                    console.log('Contact form is ready');
                });
            }
        });
    </script>

    <style>
        main {
            max-width: 800px;
            margin: 0 auto;
            padding: 2rem;
        }
        
        h1 {
            text-align: center;
            margin-bottom: 2rem;
            color: #333;
        }
    </style>
</body>
</html>

Svelte/SvelteKit

<script>
    import { onMount } from 'svelte';
    import '@izan-sanchez/izan-form';

    let formElement;
    let currentTheme = 'light';

    const fields = [
        {
            name: 'username',
            type: 'text',
            label: 'Nombre de usuario',
            required: true,
            minlength: 3,
            maxlength: 20,
            pattern: '^[a-zA-Z0-9_]+$',
            help: 'Solo letras, números y guiones bajos'
        },
        {
            name: 'email',
            type: 'email',
            label: 'Email',
            required: true
        },
        {
            name: 'age',
            type: 'number',
            label: 'Edad',
            min: 18,
            max: 120,
            required: true
        },
        {
            name: 'website',
            type: 'url',
            label: 'Sitio web',
            placeholder: 'https://tu-sitio.com'
        },
        {
            name: 'bio',
            type: 'textarea',
            label: 'Biografía',
            maxlength: 500,
            rows: 4,
            help: 'Cuéntanos un poco sobre ti'
        },
        {
            name: 'notifications',
            type: 'checkbox',
            label: 'Recibir notificaciones por email'
        },
        {
            name: 'privacy',
            type: 'checkbox',
            label: 'Acepto los términos y condiciones',
            required: true
        }
    ];

    function handleFormSubmit(event) {
        console.log('Form submitted:', event.detail.values);
        
        // Ejemplo de validación personalizada
        const { username, email, age } = event.detail.values;
        
        if (username.toLowerCase().includes('admin')) {
            alert('El nombre de usuario no puede contener "admin"');
            return;
        }
        
        if (age < 18) {
            alert('Debes ser mayor de edad');
            return;
        }
        
        // Simular envío a servidor
        setTimeout(() => {
            alert('¡Registro completado exitosamente!');
            formElement.reset();
        }, 1000);
    }

    function toggleTheme() {
        currentTheme = currentTheme === 'light' ? 'dark' : 'light';
    }

    function resetForm() {
        if (formElement) {
            formElement.reset();
        }
    }

    function validateForm() {
        if (formElement) {
            const isValid = formElement.validate();
            alert(`El formulario es ${isValid ? 'válido' : 'inválido'}`);
        }
    }

    function getFormValues() {
        if (formElement) {
            const values = formElement.getValues();
            console.log('Valores actuales:', values);
        }
    }

    onMount(() => {
        if (formElement) {
            formElement.addEventListener('formsubmit', handleFormSubmit);
            formElement.addEventListener('ready', () => {
                console.log('Form is ready');
            });
        }

        return () => {
            if (formElement) {
                formElement.removeEventListener('formsubmit', handleFormSubmit);
            }
        };
    });
</script>

<div class="container">
    <h1>Registro de Usuario</h1>
    
    <izan-form
        bind:this={formElement}
        title="Crea tu cuenta"
        submittext="Registrarse"
        loadingtext="Creando cuenta..."
        theme={currentTheme}
        fields={JSON.stringify(fields)}
        successmessage="¡Cuenta creada correctamente!"
        errormessage="Error al crear la cuenta"
        privacypolicyurl="/privacy"
        novalidate={false}
        autocomplete="on"
    />

    <div class="controls">
        <button on:click={toggleTheme}>
            Cambiar a tema {currentTheme === 'light' ? 'oscuro' : 'claro'}
        </button>
        <button on:click={resetForm}>Resetear formulario</button>
        <button on:click={validateForm}>Validar formulario</button>
        <button on:click={getFormValues}>Ver valores actuales</button>
    </div>
</div>

<style>
    .container {
        max-width: 600px;
        margin: 0 auto;
        padding: 2rem;
    }

    h1 {
        text-align: center;
        margin-bottom: 2rem;
        color: #333;
    }

    .controls {
        margin-top: 2rem;
        display: flex;
        gap: 1rem;
        flex-wrap: wrap;
        justify-content: center;
    }

    .controls button {
        padding: 0.5rem 1rem;
        border: 1px solid #ddd;
        border-radius: 4px;
        background: #fff;
        cursor: pointer;
        transition: background-color 0.2s;
    }

    .controls button:hover {
        background: #f5f5f5;
    }
</style>

👨‍💻 Autor

Izan Sanchez

📞 Soporte

¿Necesitas ayuda? Puedes: