Halaman profil yang terasa profesional biasanya tidak dibangun dari banyak komponen, melainkan dari sedikit keputusan yang dijaga konsisten. Dua keputusan paling menentukan adalah palet warna dan skala tipografi: keduanya menyentuh hampir setiap elemen di layar. Pada bagian pertama ini kita menyiapkan file styles/profil.css, mendefinisikan token warna di :root, lalu membangun skala tipografi untuk nama, peran, bio, judul subbagian, dan tautan kontak. Kartu, avatar, dan layout baru menyusul pada bagian berikutnya — fondasi dulu, bangunan kemudian.

Tujuan Bagian dan Prasyarat #

Di akhir bagian ini, halaman kita sudah menampilkan hierarki teks yang jelas pada latar abu-abu lembut, tanpa kartu dan tanpa gambar. Target spesifiknya:

  • Enam token warna didefinisikan sekali di :root dan dikonsumsi lewat var().
  • Skala tipografi empat tingkat: 36px (nama), 20px (judul subbagian), 18px (peran), 16px (teks dasar).
  • line-height: 1.5 dideklarasikan di body dan diwarisi seluruh teks.

Konsep yang dipakai sudah dibahas pada chapter Font Family , Font Size dan Weight , Line Height , Color Syntax , dan Custom Properties . Buka kembali chapter tersebut bila ada yang terasa asing.

Menyiapkan Struktur dan Markup #

Buat folder proyek halaman-profil/ berisi index.html dan subfolder styles/. Markup awal sengaja sederhana: satu wadah .halaman dan lima elemen teks.

html index.html
<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Budi Santoso — Profil</title>
  <link rel="stylesheet" href="styles/profil.css">
</head>
<body>
  <div class="halaman">
    <h1 class="profil-nama">Budi Santoso</h1>
    <p class="profil-peran">Frontend Web Developer</p>
    <p class="profil-bio">
      Saya membangun antarmuka web yang cepat, rapi, dan mudah
      dipakai. Saat ini saya sedang memperdalam CSS modern.
    </p>
    <section class="profil-bagian">
      <h2 class="profil-bagian-judul">Keahlian</h2>
    </section>
    <section class="profil-bagian">
      <h2 class="profil-bagian-judul">Kontak</h2>
      <a class="tautan-kontak" href="mailto:[email protected]">[email protected]</a>
    </section>
  </div>
</body>
</html>

Sekarang tulis potongan pertama styles/profil.css. Kelompok token diletakkan paling atas agar mudah ditemukan, kemudian aturan dasar, lalu kelas per elemen.

css styles/profil.css — Bagian 1
:root {
  --warna-teks: #0f172a;
  --warna-teks-lembut: #475569;
  --warna-aksen: #0284c7;
  --warna-latar: #f1f5f9;
  --warna-kartu: #ffffff;
  --warna-garis: #e2e8f0;
}

* {
  margin: 0;
}

body {
  font-family: system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: var(--warna-teks);
  background-color: var(--warna-latar);
}

.halaman {
  max-width: 760px;
  margin: 0 auto;
  padding: 48px 16px;
}

.profil-nama {
  font-size: 36px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--warna-teks);
}

.profil-peran {
  font-size: 18px;
  font-weight: 500;
  color: var(--warna-aksen);
}

.profil-bio {
  margin-top: 16px;
  color: var(--warna-teks-lembut);
}

.profil-bagian-judul {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--warna-teks);
}

.tautan-kontak {
  color: var(--warna-aksen);
  font-weight: 500;
  text-decoration: underline;
}

Hasil di Browser:

text
Judul "Budi Santoso" tampil 36px dengan bobot 700 dan
line-height 43.2px. Peran berwarna rgb(2, 132, 199) pada
ukuran 18px. Bio mewarisi line-height 24px dari body dan
berwarna rgb(71, 85, 105). Latar halaman rgb(241, 245, 249).

Bedah Aturan Utama #

Mari kita urai aturan-aturan kunci di atas:

  1. Blok :root: enam custom properties menyimpan warna dalam satu tempat. Token --warna-kartu dan --warna-garis belum dipakai pada bagian ini; keduanya menunggu kartu pada Bagian 2.
  2. Aturan * { margin: 0 }: reset minimal yang menghapus margin bawaan h1, h2, dan p. Dari sini setiap jarak di halaman adalah keputusan kita, bukan bawaan browser.
  3. Aturan body: font-family: system-ui, sans-serif memakai font antarmuka sistem; line-height: 1.5 pada font 16px dihitung browser menjadi 24px; color dan background-color menarik token dari :root. Karena color dan line-height bersifat inherited, seluruh teks mewarisinya tanpa deklarasi tambahan.
  4. Aturan .halaman: max-width: 760px membatasi lebar baris teks, dan margin: 0 auto mendorong wadah ke tengah viewport.
  5. Aturan .profil-nama: ukuran terbesar 36px, bobot 700, dengan line-height: 1.2 yang dihitung menjadi 43.2px — rapat untuk judul, nyaman untuk teks isi.
  6. Aturan .profil-peran dan .profil-bagian-judul: keduanya memakai bobot menengah (500 dan 600) agar tidak bersaing dengan nama. letter-spacing: 0.5px memberi ruang kecil pada judul subbagian.
  7. Aturan .tautan-kontak: text-decoration: underline menjaga konvensi tautan, sementara warna aksen menandai interaktivitas.

Uji Bagian Ini #

Halaman uji bagian ini tersedia di /tests/css/proyek/halaman-profil/typography-dan-warna.html . Halaman tersebut memuat salinan CSS di atas beserta elemen probe beratribut data-expected; ringkasannya juga tersimpan sebagai berkas tests/typography-dan-warna.spec.js yang dapat diunduh dari bagian Berkas Sumber. Asersi yang diverifikasi:

ElemenPropertiDiharapkan
bodycolorrgb(15, 23, 42)
bodybackground-colorrgb(241, 245, 249)
bodyline-height24px
.profil-namafont-size36px
.profil-namaline-height43.2px
.profil-perancolorrgb(2, 132, 199)
.profil-bagian-judulletter-spacing0.5px
.tautan-kontaktext-decoration-lineunderline

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

styles/profil.css

Unduh berkas
css
/* =========================================================
   Proyek: Halaman Profil — styles/profil.css
   File utuh proyek yang berkembang mengikuti tiga bagian:
   Bagian 1 (Typography dan Warna), Bagian 2 (Box Model dan
   Spacing), Bagian 3 (Responsive Layout). Komentar "Bagian N"
   menandai kelompok aturan yang dibahas pada setiap bagian,
   dan komentar kecil mencatat perubahan antarbagian.
   ========================================================= */

/* Bagian 1: Typography dan Warna -------------------------- */

:root {
  --warna-teks: #0f172a;
  --warna-teks-lembut: #475569;
  --warna-aksen: #0284c7;
  --warna-latar: #f1f5f9;
  --warna-kartu: #ffffff;
  --warna-garis: #e2e8f0;
}

* {
  margin: 0;
  box-sizing: border-box; /* ditambahkan pada Bagian 2 */
}

body {
  font-family: system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: var(--warna-teks);
  background-color: var(--warna-latar);
}

.halaman {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 16px; /* Bagian 3: padding dasar diturunkan dari 48px 16px */
}

.profil-nama {
  /* Bagian 3: nilai tetap 36px diganti clamp() agar fluid */
  font-size: clamp(30px, 24px + 1vw, 40px);
  font-weight: 700;
  line-height: 1.2;
  color: var(--warna-teks);
}

.profil-peran {
  font-size: 18px;
  font-weight: 500;
  color: var(--warna-aksen);
}

.profil-bio {
  color: var(--warna-teks-lembut);
}

.profil-bagian-judul {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: var(--warna-teks);
}

.tautan-kontak {
  color: var(--warna-aksen);
  font-weight: 500;
  text-decoration: underline;
}

.tautan-kontak:hover {
  text-decoration-thickness: 2px;
}

/* Bagian 2: Box Model dan Spacing ------------------------- */

.kartu-profil {
  width: min(100%, 300px); /* Bagian 3: mulanya 300px tetap */
  margin: 0 auto;
  padding: 24px;
  background-color: var(--warna-kartu);
  border: 1px solid var(--warna-garis);
  border-radius: 12px;
  box-shadow: 0 4px 12px rgb(15 23 42 / 8%);
}

.profil-kepala {
  display: flex;
  flex-direction: column; /* Bagian 3: kembali ke baris pada breakpoint */
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  text-align: center;
}

.avatar {
  flex: none; /* jaga ukuran: avatar tidak ikut menyusut */
  width: 96px;
  height: 96px;
  border-radius: 50%;
  border: 2px solid var(--warna-garis);
}

.profil-bagian {
  margin-top: 24px;
}

.daftar-keahlian {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
}

.keahlian {
  padding: 8px 12px;
  background-color: var(--warna-latar);
  border-radius: 8px;
  font-size: 14px;
  color: var(--warna-teks-lembut);
}

/* Bagian 3: Responsive Layout ----------------------------- */

@media (min-width: 640px) {
  .halaman {
    padding: 64px 32px;
  }

  .profil-kepala {
    flex-direction: row;
    text-align: left;
    margin-bottom: 24px;
  }
}

tests/box-model-dan-spacing.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 2: Box Model dan Spacing
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/halaman-profil/box-model-dan-spacing.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Kartu profil selebar 300px berkat box-sizing: border-box,
  // padding dan border tidak menambah lebar totalnya.
  { elemen: ".kartu-profil", properti: "width", diharapkan: "300px" },

  // Padding 24px di semua sisi memberi ruang lega bagi konten.
  { elemen: ".kartu-profil", properti: "padding", diharapkan: "24px" },

  // Border tipis 1px dengan warna token --warna-garis (#e2e8f0).
  { elemen: ".kartu-profil", properti: "border-top-width", diharapkan: "1px" },
  { elemen: ".kartu-profil", properti: "border-top-color", diharapkan: "rgb(226, 232, 240)" },

  // Sudut kartu membulat 12px, latar kartu putih.
  { elemen: ".kartu-profil", properti: "border-radius", diharapkan: "12px" },
  { elemen: ".kartu-profil", properti: "background-color", diharapkan: "rgb(255, 255, 255)" },

  // .profil-kepala adalah flex container dengan jarak antaritem 16px.
  { elemen: ".profil-kepala", properti: "display", diharapkan: "flex" },
  { elemen: ".profil-kepala", properti: "gap", diharapkan: "16px" },

  // Ritme vertikal: kepala kartu diberi margin bawah 16px.
  { elemen: ".profil-kepala", properti: "margin-bottom", diharapkan: "16px" },

  // Avatar berukuran 96px tetap (flex: none agar tidak menyusut
  // di dalam flex container) dengan sudut penuh (lingkaran).
  { elemen: ".avatar", properti: "width", diharapkan: "96px" },
  { elemen: ".avatar", properti: "height", diharapkan: "96px" },
  { elemen: ".avatar", properti: "border-radius", diharapkan: "50%" },

  // Setiap subbagian kartu berjarak 24px dari blok di atasnya.
  { elemen: ".profil-bagian", properti: "margin-top", diharapkan: "24px" },

  // Daftar keahlian adalah flex yang meliputi baris dengan gap 8px.
  { elemen: ".daftar-keahlian", properti: "display", diharapkan: "flex" },
  { elemen: ".daftar-keahlian", properti: "flex-wrap", diharapkan: "wrap" },
  { elemen: ".daftar-keahlian", properti: "gap", diharapkan: "8px" },

  // Chip keahlian berpadding 8px vertikal dan 12px horizontal.
  { elemen: ".keahlian", properti: "padding-top", diharapkan: "8px" },
  { elemen: ".keahlian", properti: "padding-left", diharapkan: "12px" },

  // Chip membulat 8px dengan teks 14px.
  { elemen: ".keahlian", properti: "border-radius", diharapkan: "8px" },
  { elemen: ".keahlian", properti: "font-size", diharapkan: "14px" },
];

tests/responsive-layout.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 3: Responsive Layout
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji (viewport 1280x720, sehingga breakpoint
// min-width: 640px berada pada keadaan aktif):
//   /tests/css/proyek/halaman-profil/responsive-layout.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // Wadah halaman dibatasi 760px dan, pada layar >= 640px,
  // berpadding 64px vertikal serta 32px horizontal.
  { elemen: ".halaman", properti: "max-width", diharapkan: "760px" },
  { elemen: ".halaman", properti: "padding-top", diharapkan: "64px" },

  // Kartu tetap 300px dan terpusat: margin auto terhitung browser
  // menjadi 198px pada konten selebar 696px (760 - 2 x 32).
  { elemen: ".kartu-profil", properti: "width", diharapkan: "300px" },
  { elemen: ".kartu-profil", properti: "margin-left", diharapkan: "198px" },

  // Judul fluid: clamp(30px, 24px + 1vw, 40px) pada viewport 1280px
  // menghasilkan 24 + 12.8 = 36.8px, masih di dalam rentang.
  { elemen: ".profil-nama", properti: "font-size", diharapkan: "36.8px" },

  // Pada layar >= 640px kepala profil kembali tersusun horizontal.
  { elemen: ".profil-kepala", properti: "flex-direction", diharapkan: "row" },
  { elemen: ".profil-kepala", properti: "gap", diharapkan: "16px" },

  // Jarak kepala ke bio diperbesar menjadi 24px pada breakpoint.
  { elemen: ".profil-kepala", properti: "margin-bottom", diharapkan: "24px" },

  // Daftar keahlian tetap berjajar dengan gap 8px.
  { elemen: ".daftar-keahlian", properti: "flex-direction", diharapkan: "row" },
  { elemen: ".daftar-keahlian", properti: "gap", diharapkan: "8px" },
];

tests/typography-dan-warna.spec.js

Unduh berkas
javascript
// ============================================================
// Spesifikasi uji — Proyek: Halaman Profil
// Bagian 1: Typography dan Warna
// ============================================================
// Berkas ini merangkum asersi yang diverifikasi oleh harness
// pada halaman uji:
//   /tests/css/proyek/halaman-profil/typography-dan-warna.html
// Setiap entri memasangkan elemen probe dengan properti CSS
// dan nilai computed yang diharapkan.

const asersi = [
  // body membawa warna teks utama dari token --warna-teks (#0f172a
  // dikonversi browser menjadi rgb(15, 23, 42)).
  { elemen: "body", properti: "color", diharapkan: "rgb(15, 23, 42)" },

  // Latar halaman memakai token --warna-latar (#f1f5f9).
  { elemen: "body", properti: "background-color", diharapkan: "rgb(241, 245, 249)" },

  // Ukuran teks dasar 16px dengan line-height 1.5 (16 x 1.5 = 24px).
  { elemen: "body", properti: "font-size", diharapkan: "16px" },
  { elemen: "body", properti: "line-height", diharapkan: "24px" },

  // .profil-nama berada di puncak skala tipografi: 36px, tebal 700.
  { elemen: ".profil-nama", properti: "font-size", diharapkan: "36px" },
  { elemen: ".profil-nama", properti: "font-weight", diharapkan: "700" },

  // line-height 1.2 pada font 36px dihitung browser menjadi 43.2px.
  { elemen: ".profil-nama", properti: "line-height", diharapkan: "43.2px" },

  // .profil-peran memakai warna aksen (#0284c7) dan bobot medium.
  { elemen: ".profil-peran", properti: "color", diharapkan: "rgb(2, 132, 199)" },
  { elemen: ".profil-peran", properti: "font-size", diharapkan: "18px" },
  { elemen: ".profil-peran", properti: "font-weight", diharapkan: "500" },

  // .profil-bio memakai warna teks lembut (#475569).
  { elemen: ".profil-bio", properti: "color", diharapkan: "rgb(71, 85, 105)" },

  // Judul bagian 20px, bobot 600, dengan letter-spacing 0.5px.
  { elemen: ".profil-bagian-judul", properti: "font-size", diharapkan: "20px" },
  { elemen: ".profil-bagian-judul", properti: "font-weight", diharapkan: "600" },
  { elemen: ".profil-bagian-judul", properti: "letter-spacing", diharapkan: "0.5px" },

  // Tautan kontak berwarna aksen dan digarisbawahi.
  { elemen: ".tautan-kontak", properti: "color", diharapkan: "rgb(2, 132, 199)" },
  { elemen: ".tautan-kontak", properti: "text-decoration-line", diharapkan: "underline" },
];