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.
<div class="team">
<article class="member">
<span class="member__ava" aria-hidden="true">AN</span>
<b>Alif Nugraha</b>
<small>Founder & 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>.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
gapis 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?