Docs menu

Components · · 3 min read

Company-profile component: team cards & a company timeline

A team grid with dividers from a 1px gap (no doubled borders), a timeline from one pseudo-element, initial avatars with place-items, and responsiveness without media queries — editor + live preview.

HTMLCSSGridCompany profile
Live exampleRendered by the actual component — not an image.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div class="team">
  <article class="member">
    <span class="member__ava" aria-hidden="true">AN</span>
    <b>Alif Nugraha</b>
    <small>Founder &amp; Lead Engineer</small>
  </article>
  <article class="member">
    <span class="member__ava" aria-hidden="true">DW</span>
    <b>Dewi Wulandari</b>
    <small>Project Manager</small>
  </article>
  <article class="member">
    <span class="member__ava" aria-hidden="true">BP</span>
    <b>Bayu Pratama</b>
    <small>Site Supervisor</small>
  </article>
</div>

<ol class="timeline">
  <li>
    <span class="timeline__year">2015</span>
    <p>Berdiri dengan 3 orang dan satu proyek kantor 120m².</p>
  </li>
  <li>
    <span class="timeline__year">2019</span>
    <p>Proyek ke-50 selesai; tim lapangan tetap pertama direkrut.</p>
  </li>
  <li>
    <span class="timeline__year">2024</span>
    <p>120+ proyek, ekspansi layanan ke MEP dan furniture custom.</p>
  </li>
</ol>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
.team {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px;
  background: #e4e4e7;          /* garis pemisah = gap yang terlihat */
  border: 1px solid #e4e4e7;
}
.member {
  background: #fff;
  padding: 18px 12px;
  text-align: center;
}
.member__ava {
  width: 44px;
  height: 44px;
  margin: 0 auto 10px;
  border-radius: 999px;
  background: #f0fdfa;
  color: #0f766e;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 600;
}
.member b { display: block; font-size: 13px; }
.member small { display: block; margin-top: 2px; font-size: 11px; color: #71717a; }

/* Timeline: satu garis vertikal dari ::before induknya */
.timeline {
  position: relative;
  margin: 24px 0 0;
  padding: 0 0 0 26px;
  list-style: none;
}
.timeline::before {
  content: "";
  position: absolute;
  left: 5px;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: #e4e4e7;
}
.timeline li {
  position: relative;
  padding-bottom: 16px;
}
.timeline li::before {
  content: "";
  position: absolute;
  left: -26px;
  top: 5px;
  width: 11px;
  height: 11px;
  border-radius: 999px;
  background: #fff;
  border: 2px solid #0d9488;
}
.timeline__year {
  font-size: 11px;
  letter-spacing: 0.14em;
  color: #0d9488;
  font-weight: 600;
}
.timeline p {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.6;
  color: #52525b;
}

Preview

Two mandatory company-profile sections: who the people are, and how long the company has been running. Both are in the preview panel above — and both are CSS only.

Divider lines from gap, not from border

The team grid in the example uses no border on individual cards. Instead, the container has a grey background and a 1px gap:

.team {
  display: grid;
  gap: 1px;
  background: #e4e4e7;   /* what shows through the gaps = the lines */
  border: 1px solid #e4e4e7;
}
.member { background: #fff; }

The result is a perfect divider grid: no doubled borders where two cards meet, no :last-child rule to strip the final border, and it stays tidy when cards wrap to the next row. I use this pattern in nearly every grid on this site.

Timeline: one line, one pseudo-element

Timelines are often built with a library or a stack of <div>s. All you need is one vertical line from the parent’s ::before, plus one dot from each item’s ::before:

.timeline::before {          /* the vertical line */
  content: "";
  position: absolute;
  left: 5px; top: 6px; bottom: 6px;
  width: 1px;
  background: #e4e4e7;
}
.timeline li::before {       /* the dot at each year */
  content: "";
  position: absolute;
  left: -26px; top: 5px;
  width: 11px; height: 11px;
  border-radius: 999px;
  background: #fff;
  border: 2px solid #0d9488;
}

The key is top: 6px; bottom: 6px on the line — it stops exactly at the first and last dots instead of overshooting. That small detail separates a timeline that looks finished from one that looks unfinished.

Note the element too: <ol>, not <div>. A company history is ordered, and that order is meaningful — screen readers announce “list, 3 items” and number them.

place-items: center — two-line centering

The initial avatars are centered with:

display: grid;
place-items: center;

Two lines replace the old line-height-equals-height trick (which breaks the moment the text is two characters at a different font size) or position: absolute + transform: translate(-50%, -50%).

Responsive without media queries

repeat(auto-fit, minmax(140px, 1fr)) lets the team column count adapt on its own: 3 columns on desktop, 2 on tablets, 1 on phones — without writing a single breakpoint. Narrow your browser window and watch the preview panel follow.

Lessons

  • A container background plus a 1px gap is the cheapest way to get grid dividers that are always correct.
  • Pseudo-elements save dozens of empty elements in your HTML — timelines, rules, and badges can nearly always be built this way.
  • Pick elements by meaning (<ol> for sequence), not by appearance.

Want something like this built for your business?