Hibuno

HTML

Teknik HTML modern untuk aplikasi web yang kompleks dan accessible

Pengenalan

HTML modern bukan hanya tentang markup sederhana. courses ini akan mengajarkan Anda semantic HTML, accessibility, form validation, dan teknik-teknik advanced untuk membangun aplikasi web yang robust.

Semantic HTML

Mengapa Semantic HTML Penting?

Semantic HTML memberikan makna pada struktur dokumen, membantu:

  • Search engines memahami konten
  • Screen readers memberikan pengalaman yang lebih baik
  • Developer memahami struktur kode
  • Maintenance yang lebih mudah

Elemen Semantic Utama

<!-- Header dengan navigasi -->
<header>
  <nav>
    <ul>
      <li><a href="/">Beranda</a></li>
      <li><a href="/tentang">Tentang</a></li>
      <li><a href="/kontak">Kontak</a></li>
    </ul>
  </nav>
</header>

<!-- Konten utama -->
<main>
  <!-- Artikel dengan struktur lengkap -->
  <article>
    <header>
      <h1>Judul Artikel</h1>
      <p>
        <time datetime="2024-01-15">15 Januari 2024</time>
        oleh <address>John Doe</address>
      </p>
    </header>
    
    <section>
      <h2>Pendahuluan</h2>
      <p>Konten pendahuluan...</p>
    </section>
    
    <section>
      <h2>Pembahasan</h2>
      <p>Konten pembahasan...</p>
    </section>
    
    <footer>
      <p>Tags: <a href="/tag/html">HTML</a>, <a href="/tag/web">Web</a></p>
    </footer>
  </article>
  
  <!-- Sidebar dengan konten terkait -->
  <aside>
    <h2>Artikel Terkait</h2>
    <ul>
      <li><a href="/artikel-1">Artikel 1</a></li>
      <li><a href="/artikel-2">Artikel 2</a></li>
    </ul>
  </aside>
</main>

<!-- Footer -->
<footer>
  <p>&copy; 2024 Perusahaan Anda. Hak cipta dilindungi.</p>
</footer>

Figure dan Figcaption

<figure>
  <img src="/grafik-penjualan.png" alt="Grafik penjualan Q1 2024">
  <figcaption>
    Grafik menunjukkan peningkatan penjualan sebesar 45% di Q1 2024
  </figcaption>
</figure>

<figure>
  <pre><code>
function hello() {
  console.log('Hello World');
}
  </code></pre>
  <figcaption>Contoh fungsi JavaScript sederhana</figcaption>
</figure>

Details dan Summary

<details>
  <summary>Apa itu HTML Semantic?</summary>
  <p>
    HTML Semantic adalah penggunaan elemen HTML yang memberikan makna
    pada struktur konten, bukan hanya presentasi visual.
  </p>
</details>

<details open>
  <summary>FAQ: Bagaimana cara memulai?</summary>
  <p>Mulai dengan mempelajari elemen-elemen semantic dasar seperti
     header, nav, main, article, section, aside, dan footer.</p>
</details>

Form Advanced

Form Validation HTML5

<form action="/submit" method="POST" novalidate>
  <!-- Email dengan validasi -->
  <div>
    <label for="email">Email:</label>
    <input 
      type="email" 
      id="email" 
      name="email"
      required
      pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
      placeholder="nama@example.com"
    >
    <span class="error" aria-live="polite"></span>
  </div>
  
  <!-- Password dengan requirements -->
  <div>
    <label for="password">Password:</label>
    <input 
      type="password" 
      id="password" 
      name="password"
      required
      minlength="8"
      pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
      title="Minimal 8 karakter, harus mengandung huruf besar, huruf kecil, dan angka"
    >
    <span class="error" aria-live="polite"></span>
  </div>
  
  <!-- Number dengan range -->
  <div>
    <label for="age">Umur:</label>
    <input 
      type="number" 
      id="age" 
      name="age"
      min="18"
      max="100"
      step="1"
      required
    >
  </div>
  
  <!-- Date dengan range -->
  <div>
    <label for="birthdate">Tanggal Lahir:</label>
    <input 
      type="date" 
      id="birthdate" 
      name="birthdate"
      min="1920-01-01"
      max="2006-12-31"
      required
    >
  </div>
  
  <!-- URL validation -->
  <div>
    <label for="website">Website:</label>
    <input 
      type="url" 
      id="website" 
      name="website"
      placeholder="https://example.com"
    >
  </div>
  
  <!-- Tel dengan pattern -->
  <div>
    <label for="phone">Telepon:</label>
    <input 
      type="tel" 
      id="phone" 
      name="phone"
      pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
      placeholder="123-456-7890"
    >
  </div>
  
  <button type="submit">Kirim</button>
</form>

Custom Validation dengan JavaScript

<form id="registrationForm">
  <div>
    <label for="username">Username:</label>
    <input 
      type="text" 
      id="username" 
      name="username"
      required
      minlength="3"
    >
    <span class="error"></span>
  </div>
  
  <div>
    <label for="confirmPassword">Konfirmasi Password:</label>
    <input 
      type="password" 
      id="confirmPassword" 
      name="confirmPassword"
      required
    >
    <span class="error"></span>
  </div>
  
  <button type="submit">Daftar</button>
</form>

<script>
const form = document.getElementById('registrationForm');
const username = document.getElementById('username');
const password = document.getElementById('password');
const confirmPassword = document.getElementById('confirmPassword');

// Custom validation
username.addEventListener('input', function() {
  if (username.validity.valueMissing) {
    username.setCustomValidity('Username harus diisi');
  } else if (username.validity.tooShort) {
    username.setCustomValidity('Username minimal 3 karakter');
  } else if (!/^[a-zA-Z0-9_]+$/.test(username.value)) {
    username.setCustomValidity('Username hanya boleh huruf, angka, dan underscore');
  } else {
    username.setCustomValidity('');
  }
  username.reportValidity();
});

// Password match validation
confirmPassword.addEventListener('input', function() {
  if (confirmPassword.value !== password.value) {
    confirmPassword.setCustomValidity('Password tidak cocok');
  } else {
    confirmPassword.setCustomValidity('');
  }
  confirmPassword.reportValidity();
});

form.addEventListener('submit', function(e) {
  if (!form.checkValidity()) {
    e.preventDefault();
    // Show all errors
    const inputs = form.querySelectorAll('input');
    inputs.forEach(input => {
      const error = input.nextElementSibling;
      if (!input.validity.valid) {
        error.textContent = input.validationMessage;
      } else {
        error.textContent = '';
      }
    });
  }
});
</script>

Input Types Modern

<!-- Color picker -->
<label for="color">Pilih Warna:</label>
<input type="color" id="color" name="color" value="#ff0000">

<!-- Range slider -->
<label for="volume">Volume:</label>
<input type="range" id="volume" name="volume" min="0" max="100" value="50">
<output for="volume">50</output>

<!-- File upload dengan accept -->
<label for="avatar">Upload Avatar:</label>
<input 
  type="file" 
  id="avatar" 
  name="avatar"
  accept="image/png, image/jpeg"
  multiple
>

<!-- Search dengan datalist -->
<label for="search">Cari Produk:</label>
<input 
  type="search" 
  id="search" 
  name="search"
  list="products"
  autocomplete="off"
>
<datalist id="products">
  <option value="Laptop">
  <option value="Mouse">
  <option value="Keyboard">
  <option value="Monitor">
</datalist>

Accessibility (A11y)

ARIA Attributes

<!-- Navigation dengan ARIA -->
<nav aria-label="Navigasi Utama">
  <ul role="menubar">
    <li role="none">
      <a href="/" role="menuitem">Beranda</a>
    </li>
    <li role="none">
      <a href="/produk" role="menuitem" aria-haspopup="true" aria-expanded="false">
        Produk
      </a>
      <ul role="menu" aria-label="Submenu Produk" hidden>
        <li role="none">
          <a href="/produk/laptop" role="menuitem">Laptop</a>
        </li>
        <li role="none">
          <a href="/produk/phone" role="menuitem">Phone</a>
        </li>
      </ul>
    </li>
  </ul>
</nav>

<!-- Alert messages -->
<div role="alert" aria-live="assertive" aria-atomic="true">
  <p>Formulir berhasil dikirim!</p>
</div>

<!-- Loading state -->
<button aria-busy="true" aria-label="Memuat data...">
  <span aria-hidden="true">⏳</span>
  Memuat...
</button>

<!-- Tab interface -->
<div role="tablist" aria-label="Pengaturan Akun">
  <button 
    role="tab" 
    aria-selected="true" 
    aria-controls="profile-panel"
    id="profile-tab"
  >
    Profil
  </button>
  <button 
    role="tab" 
    aria-selected="false" 
    aria-controls="security-panel"
    id="security-tab"
  >
    Keamanan
  </button>
</div>

<div role="tabpanel" id="profile-panel" aria-labelledby="profile-tab">
  <h2>Pengaturan Profil</h2>
  <!-- Konten profil -->
</div>

<div role="tabpanel" id="security-panel" aria-labelledby="security-tab" hidden>
  <h2>Pengaturan Keamanan</h2>
  <!-- Konten keamanan -->
</div>
<body>
  <a href="#main-content" class="skip-link">
    Lewati ke konten utama
  </a>
  
  <header>
    <!-- Header content -->
  </header>
  
  <main id="main-content" tabindex="-1">
    <!-- Main content -->
  </main>
</body>

<style>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 8px;
  text-decoration: none;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}
</style>

Focus Management

<!-- Visible focus indicator -->
<style>
:focus {
  outline: 3px solid #4A90E2;
  outline-offset: 2px;
}

:focus:not(:focus-visible) {
  outline: none;
}

:focus-visible {
  outline: 3px solid #4A90E2;
  outline-offset: 2px;
}
</style>

<!-- Modal dengan focus trap -->
<div role="dialog" aria-labelledby="modal-title" aria-modal="true">
  <h2 id="modal-title">Konfirmasi Hapus</h2>
  <p>Apakah Anda yakin ingin menghapus item ini?</p>
  <button>Batal</button>
  <button>Hapus</button>
</div>

<script>
// Focus trap implementation
const modal = document.querySelector('[role="dialog"]');
const focusableElements = modal.querySelectorAll(
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];

modal.addEventListener('keydown', function(e) {
  if (e.key === 'Tab') {
    if (e.shiftKey) {
      if (document.activeElement === firstFocusable) {
        lastFocusable.focus();
        e.preventDefault();
      }
    } else {
      if (document.activeElement === lastFocusable) {
        firstFocusable.focus();
        e.preventDefault();
      }
    }
  }
  
  if (e.key === 'Escape') {
    closeModal();
  }
});
</script>

Meta Tags dan SEO

Essential Meta Tags

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <!-- Primary Meta Tags -->
  <title>Judul Halaman - Nama Website</title>
  <meta name="title" content="Judul Halaman - Nama Website">
  <meta name="description" content="Deskripsi halaman yang menarik dan informatif, maksimal 160 karakter">
  <meta name="keywords" content="keyword1, keyword2, keyword3">
  <meta name="author" content="Nama Penulis">
  <meta name="robots" content="index, follow">
  
  <!-- Open Graph / Facebook -->
  <meta property="og:type" content="website">
  <meta property="og:url" content="https://example.com/">
  <meta property="og:title" content="Judul Halaman - Nama Website">
  <meta property="og:description" content="Deskripsi halaman yang menarik">
  <meta property="og:image" content="https://example.com/image.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  
  <!-- Twitter -->
  <meta property="twitter:card" content="summary_large_image">
  <meta property="twitter:url" content="https://example.com/">
  <meta property="twitter:title" content="Judul Halaman - Nama Website">
  <meta property="twitter:description" content="Deskripsi halaman yang menarik">
  <meta property="twitter:image" content="https://example.com/image.jpg">
  
  <!-- Favicon -->
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
  <link rel="manifest" href="/site.webmanifest">
  
  <!-- Canonical URL -->
  <link rel="canonical" href="https://example.com/halaman-ini">
  
  <!-- Alternate languages -->
  <link rel="alternate" hreflang="id" href="https://example.com/id/halaman">
  <link rel="alternate" hreflang="en" href="https://example.com/en/page">
</head>
<body>
  <!-- Content -->
</body>
</html>

Structured Data (JSON-LD)

<!-- Article Schema -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Judul Artikel",
  "image": "https://example.com/image.jpg",
  "author": {
    "@type": "Person",
    "name": "John Doe"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Nama Organisasi",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/logo.png"
    }
  },
  "datePublished": "2024-01-15",
  "dateModified": "2024-01-20"
}
</script>

<!-- Product Schema -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Nama Produk",
  "image": "https://example.com/product.jpg",
  "description": "Deskripsi produk",
  "brand": {
    "@type": "Brand",
    "name": "Nama Brand"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/product",
    "priceCurrency": "IDR",
    "price": "1000000",
    "availability": "https://schema.org/InStock"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.5",
    "reviewCount": "89"
  }
}
</script>

Web Components

Custom Elements

<user-card 
  name="John Doe" 
  email="john@example.com"
  avatar="/avatar.jpg"
></user-card>

<script>
class UserCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  
  connectedCallback() {
    const name = this.getAttribute('name');
    const email = this.getAttribute('email');
    const avatar = this.getAttribute('avatar');
    
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          border: 1px solid #ddd;
          border-radius: 8px;
          padding: 16px;
          max-width: 300px;
        }
        
        .avatar {
          width: 64px;
          height: 64px;
          border-radius: 50%;
          object-fit: cover;
        }
        
        .name {
          font-size: 18px;
          font-weight: bold;
          margin: 8px 0 4px;
        }
        
        .email {
          color: #666;
          font-size: 14px;
        }
      </style>
      
      <div class="card">
        <img src="${avatar}" alt="${name}" class="avatar">
        <div class="name">${name}</div>
        <div class="email">${email}</div>
        <slot></slot>
      </div>
    `;
  }
  
  static get observedAttributes() {
    return ['name', 'email', 'avatar'];
  }
  
  attributeChangedCallback(name, oldValue, newValue) {
    if (oldValue !== newValue) {
      this.connectedCallback();
    }
  }
}

customElements.define('user-card', UserCard);
</script>

Template dan Slot

<template id="product-card-template">
  <style>
    .product-card {
      border: 1px solid #ddd;
      padding: 16px;
      border-radius: 8px;
    }
    
    .product-image {
      width: 100%;
      height: 200px;
      object-fit: cover;
      border-radius: 4px;
    }
    
    .product-title {
      font-size: 18px;
      font-weight: bold;
      margin: 12px 0 8px;
    }
    
    .product-price {
      color: #e74c3c;
      font-size: 20px;
      font-weight: bold;
    }
  </style>
  
  <div class="product-card">
    <img class="product-image" src="" alt="">
    <h3 class="product-title"></h3>
    <div class="product-price"></div>
    <slot name="actions"></slot>
  </div>
</template>

<product-card 
  image="/product.jpg" 
  title="Nama Produk" 
  price="Rp 1.000.000"
>
  <div slot="actions">
    <button>Beli Sekarang</button>
    <button>Tambah ke Keranjang</button>
  </div>
</product-card>

<script>
class ProductCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    const template = document.getElementById('product-card-template');
    const content = template.content.cloneNode(true);
    
    this.shadowRoot.appendChild(content);
  }
  
  connectedCallback() {
    const image = this.getAttribute('image');
    const title = this.getAttribute('title');
    const price = this.getAttribute('price');
    
    this.shadowRoot.querySelector('.product-image').src = image;
    this.shadowRoot.querySelector('.product-image').alt = title;
    this.shadowRoot.querySelector('.product-title').textContent = title;
    this.shadowRoot.querySelector('.product-price').textContent = price;
  }
}

customElements.define('product-card', ProductCard);
</script>

Performance Optimization

Lazy Loading

<!-- Images -->
<img 
  src="placeholder.jpg" 
  data-src="actual-image.jpg" 
  alt="Description"
  loading="lazy"
  width="800"
  height="600"
>

<!-- Iframes -->
<iframe 
  src="https://www.youtube.com/embed/VIDEO_ID" 
  loading="lazy"
  width="560" 
  height="315"
  title="Video Title"
></iframe>

<!-- Native lazy loading dengan IntersectionObserver fallback -->
<script>
if ('loading' in HTMLImageElement.prototype) {
  // Browser supports native lazy loading
  const images = document.querySelectorAll('img[loading="lazy"]');
  images.forEach(img => {
    img.src = img.dataset.src;
  });
} else {
  // Fallback to IntersectionObserver
  const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;
        img.classList.remove('lazy');
        observer.unobserve(img);
      }
    });
  });
  
  const images = document.querySelectorAll('img.lazy');
  images.forEach(img => imageObserver.observe(img));
}
</script>

Resource Hints

<head>
  <!-- DNS Prefetch -->
  <link rel="dns-prefetch" href="https://fonts.googleapis.com">
  
  <!-- Preconnect -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  
  <!-- Prefetch -->
  <link rel="prefetch" href="/next-page.html">
  
  <!-- Preload -->
  <link rel="preload" href="/critical.css" as="style">
  <link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
  
  <!-- Prerender (use carefully) -->
  <link rel="prerender" href="/likely-next-page.html">
</head>

Latihan Praktis

Project 1: Form Registrasi Lengkap

Buat form registrasi dengan:

  • Validasi email, password, dan konfirmasi password
  • Password strength indicator
  • Terms and conditions checkbox
  • Accessible error messages
  • Loading state saat submit

Project 2: Accessible Navigation

Buat navigation menu dengan:

  • Keyboard navigation (Tab, Arrow keys, Enter, Escape)
  • ARIA attributes yang tepat
  • Mobile responsive dengan hamburger menu
  • Focus management
  • Skip links

Project 3: Product Card Component

Buat Web Component untuk product card dengan:

  • Shadow DOM
  • Slots untuk custom content
  • Observed attributes
  • Event handling
  • Styling yang encapsulated

Kesimpulan

HTML modern adalah fondasi yang kuat untuk aplikasi web. Dengan memahami semantic HTML, accessibility, form validation, dan Web Components, Anda dapat membangun aplikasi yang:

  • Mudah di-maintain
  • Accessible untuk semua pengguna
  • SEO-friendly
  • Performant
  • Scalable

Langkah Selanjutnya

On this page