32 CSS Timelines07 / 32
Team Swimlane Gantt Timeline — Grid-Line Placement, No Libraries
A real project Gantt without a charting library: team swimlanes as grid rows, a 12-week header, and every task bar positioned purely by grid-column line numbers — change a task's start and length by editing two integers in the HTML. Week gridlines come from one repeating gradient; the current week is a highlighted column band.
Published
The code
<section class="tls-07" aria-label="Team gantt timeline demo">
<div class="tls-07__inner">
<header class="tls-07__head"><h3 class="tls-07__title">Q3 delivery plan</h3><p class="tls-07__sub">12 weeks · 3 teams</p></header>
<div class="tls-07__chart">
<div class="tls-07__weeks" aria-hidden="true">
<span></span><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span><span>W7</span><span>W8</span><span>W9</span><span>W10</span><span>W11</span><span>W12</span>
</div>
<div class="tls-07__lanes">
<div class="tls-07__nowband" aria-hidden="true"></div>
<p class="tls-07__lane">Design</p>
<!-- grid-column: (week + 1) / span duration -->
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:2/span 3;grid-row:1"><span class="tls-07__bfill" style="width:100%"></span><span class="tls-07__bname">Discovery<span class="tls-07__sr"> — weeks 1 to 3, done</span></span></div>
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:5/span 4;grid-row:1"><span class="tls-07__bfill" style="width:60%"></span><span class="tls-07__bname">Design system<span class="tls-07__sr"> — weeks 4 to 7, 60% done</span></span></div>
<p class="tls-07__lane" style="grid-row:2">Engineering</p>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:4/span 5;grid-row:2"><span class="tls-07__bfill" style="width:75%"></span><span class="tls-07__bname">Core build<span class="tls-07__sr"> — weeks 3 to 7, 75% done</span></span></div>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:9/span 4;grid-row:2"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Hardening<span class="tls-07__sr"> — weeks 8 to 11, not started</span></span></div>
<p class="tls-07__lane" style="grid-row:3">Marketing</p>
<div class="tls-07__bar" style="--c:#b45309;grid-column:7/span 3;grid-row:3"><span class="tls-07__bfill" style="width:30%"></span><span class="tls-07__bname">Launch prep<span class="tls-07__sr"> — weeks 6 to 8, 30% done</span></span></div>
<div class="tls-07__bar" style="--c:#b45309;grid-column:11/span 3;grid-row:3"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Campaign<span class="tls-07__sr"> — weeks 10 to 12, not started</span></span></div>
</div>
</div>
</div>
</section><section class="tls-07" aria-label="Team gantt timeline demo">
<div class="tls-07__inner">
<header class="tls-07__head"><h3 class="tls-07__title">Q3 delivery plan</h3><p class="tls-07__sub">12 weeks · 3 teams</p></header>
<div class="tls-07__chart">
<div class="tls-07__weeks" aria-hidden="true">
<span></span><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span><span>W7</span><span>W8</span><span>W9</span><span>W10</span><span>W11</span><span>W12</span>
</div>
<div class="tls-07__lanes">
<div class="tls-07__nowband" aria-hidden="true"></div>
<p class="tls-07__lane">Design</p>
<!-- grid-column: (week + 1) / span duration -->
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:2/span 3;grid-row:1"><span class="tls-07__bfill" style="width:100%"></span><span class="tls-07__bname">Discovery<span class="tls-07__sr"> — weeks 1 to 3, done</span></span></div>
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:5/span 4;grid-row:1"><span class="tls-07__bfill" style="width:60%"></span><span class="tls-07__bname">Design system<span class="tls-07__sr"> — weeks 4 to 7, 60% done</span></span></div>
<p class="tls-07__lane" style="grid-row:2">Engineering</p>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:4/span 5;grid-row:2"><span class="tls-07__bfill" style="width:75%"></span><span class="tls-07__bname">Core build<span class="tls-07__sr"> — weeks 3 to 7, 75% done</span></span></div>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:9/span 4;grid-row:2"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Hardening<span class="tls-07__sr"> — weeks 8 to 11, not started</span></span></div>
<p class="tls-07__lane" style="grid-row:3">Marketing</p>
<div class="tls-07__bar" style="--c:#b45309;grid-column:7/span 3;grid-row:3"><span class="tls-07__bfill" style="width:30%"></span><span class="tls-07__bname">Launch prep<span class="tls-07__sr"> — weeks 6 to 8, 30% done</span></span></div>
<div class="tls-07__bar" style="--c:#b45309;grid-column:11/span 3;grid-row:3"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Campaign<span class="tls-07__sr"> — weeks 10 to 12, not started</span></span></div>
</div>
</div>
</div>
</section>.tls-07,
.tls-07 *,
.tls-07 *::before,
.tls-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-07 {
min-height: 100vh;
width: 100%;
--tl-line: #e7e5e4;
--tl-ink: #1c1917;
--tl-mut: #78716c;
--label: 110px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #fafaf9;
padding: 64px 24px;
display: grid;
place-items: center;
}
.tls-07__inner {
width: min(880px,100%);
}
.tls-07__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.tls-07__title {
font-size: clamp(20px,3.5vw,26px);
font-weight: 800;
color: var(--tl-ink);
letter-spacing: -.02em;
}
.tls-07__sub {
font-size: 12.5px;
color: var(--tl-mut);
}
.tls-07__chart {
background: #fff;
border: 1px solid var(--tl-line);
border-radius: 14px;
padding: 18px 20px 22px;
overflow: hidden;
}
.tls-07__weeks {
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
font-size: 10px;
font-weight: 700;
letter-spacing: .06em;
color: var(--tl-mut);
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid var(--tl-line);
margin-bottom: 14px;
}
.tls-07__lanes {
position: relative;
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
grid-auto-rows: 44px;
row-gap: 10px;
align-items: center;
background: repeating-linear-gradient(90deg,transparent 0,transparent calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12));
background-position: var(--label) 0;
background-repeat: no-repeat;
background-size: calc(100% - var(--label)) 100%;
background-origin: padding-box;
}
.tls-07__nowband {
grid-column: 9;
grid-row: 1/-1;
height: 100%;
background: rgb(220 38 38 / .06);
border-inline: 1px dashed rgb(220 38 38 / .4);
}
.tls-07__lane {
grid-column: 1;
font-size: 12.5px;
font-weight: 700;
color: var(--tl-mut);
padding-right: 14px;
}
.tls-07__bar {
position: relative;
height: 30px;
border-radius: 8px;
background: color-mix(in srgb,var(--c) 14%,#fff);
border: 1px solid color-mix(in srgb,var(--c) 40%,#fff);
overflow: hidden;
display: flex;
align-items: center;
}
.tls-07__bfill {
position: absolute;
inset: 0 auto 0 0;
background: color-mix(in srgb,var(--c) 26%,#fff);
}
.tls-07__bname {
position: relative;
font-size: 11px;
font-weight: 700;
color: var(--c);
padding-inline: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tls-07__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@media (max-width: 640px) {
.tls-07__weeks {
display: none;
}
.tls-07__lanes {
grid-template-columns: 1fr;
grid-auto-rows: auto;
background: none;
}
.tls-07__nowband {
display: none;
}
.tls-07__lane {
grid-row: auto!important;
padding: 6px 0 2px;
}
.tls-07__bar {
grid-column: 1!important;
grid-row: auto!important;
}
}.tls-07,
.tls-07 *,
.tls-07 *::before,
.tls-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-07 {
min-height: 100vh;
width: 100%;
--tl-line: #e7e5e4;
--tl-ink: #1c1917;
--tl-mut: #78716c;
--label: 110px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #fafaf9;
padding: 64px 24px;
display: grid;
place-items: center;
}
.tls-07__inner {
width: min(880px,100%);
}
.tls-07__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.tls-07__title {
font-size: clamp(20px,3.5vw,26px);
font-weight: 800;
color: var(--tl-ink);
letter-spacing: -.02em;
}
.tls-07__sub {
font-size: 12.5px;
color: var(--tl-mut);
}
.tls-07__chart {
background: #fff;
border: 1px solid var(--tl-line);
border-radius: 14px;
padding: 18px 20px 22px;
overflow: hidden;
}
.tls-07__weeks {
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
font-size: 10px;
font-weight: 700;
letter-spacing: .06em;
color: var(--tl-mut);
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid var(--tl-line);
margin-bottom: 14px;
}
.tls-07__lanes {
position: relative;
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
grid-auto-rows: 44px;
row-gap: 10px;
align-items: center;
background: repeating-linear-gradient(90deg,transparent 0,transparent calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12));
background-position: var(--label) 0;
background-repeat: no-repeat;
background-size: calc(100% - var(--label)) 100%;
background-origin: padding-box;
}
.tls-07__nowband {
grid-column: 9;
grid-row: 1/-1;
height: 100%;
background: rgb(220 38 38 / .06);
border-inline: 1px dashed rgb(220 38 38 / .4);
}
.tls-07__lane {
grid-column: 1;
font-size: 12.5px;
font-weight: 700;
color: var(--tl-mut);
padding-right: 14px;
}
.tls-07__bar {
position: relative;
height: 30px;
border-radius: 8px;
background: color-mix(in srgb,var(--c) 14%,#fff);
border: 1px solid color-mix(in srgb,var(--c) 40%,#fff);
overflow: hidden;
display: flex;
align-items: center;
}
.tls-07__bfill {
position: absolute;
inset: 0 auto 0 0;
background: color-mix(in srgb,var(--c) 26%,#fff);
}
.tls-07__bname {
position: relative;
font-size: 11px;
font-weight: 700;
color: var(--c);
padding-inline: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tls-07__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@media (max-width: 640px) {
.tls-07__weeks {
display: none;
}
.tls-07__lanes {
grid-template-columns: 1fr;
grid-auto-rows: auto;
background: none;
}
.tls-07__nowband {
display: none;
}
.tls-07__lane {
grid-row: auto!important;
padding: 6px 0 2px;
}
.tls-07__bar {
grid-column: 1!important;
grid-row: auto!important;
}
}/* No JavaScript — every bar is grid-column: start / span duration (two integers = the whole API); week gridlines are one repeating-linear-gradient sized to the fr tracks. */
/* No JavaScript — every bar is grid-column: start / span duration (two integers = the whole API); week gridlines are one repeating-linear-gradient sized to the fr tracks. */Here's a working CSS Timeline from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Team Swimlane Gantt Timeline — Grid-Line Placement, No Libraries
Source: https://codefronts.com/layouts/css-timelines/team-swimlane-gantt-timeline-grid-line-placement-no-libraries/
A real project Gantt without a charting library: team swimlanes as grid rows, a 12-week header, and every task bar positioned purely by grid-column line numbers — change a task's start and length by editing two integers in the HTML. Week gridlines come from one repeating gradient; the current week is a highlighted column band.
## HTML
```html
<section class="tls-07" aria-label="Team gantt timeline demo">
<div class="tls-07__inner">
<header class="tls-07__head"><h3 class="tls-07__title">Q3 delivery plan</h3><p class="tls-07__sub">12 weeks · 3 teams</p></header>
<div class="tls-07__chart">
<div class="tls-07__weeks" aria-hidden="true">
<span></span><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span><span>W7</span><span>W8</span><span>W9</span><span>W10</span><span>W11</span><span>W12</span>
</div>
<div class="tls-07__lanes">
<div class="tls-07__nowband" aria-hidden="true"></div>
<p class="tls-07__lane">Design</p>
<!-- grid-column: (week + 1) / span duration -->
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:2/span 3;grid-row:1"><span class="tls-07__bfill" style="width:100%"></span><span class="tls-07__bname">Discovery<span class="tls-07__sr"> — weeks 1 to 3, done</span></span></div>
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:5/span 4;grid-row:1"><span class="tls-07__bfill" style="width:60%"></span><span class="tls-07__bname">Design system<span class="tls-07__sr"> — weeks 4 to 7, 60% done</span></span></div>
<p class="tls-07__lane" style="grid-row:2">Engineering</p>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:4/span 5;grid-row:2"><span class="tls-07__bfill" style="width:75%"></span><span class="tls-07__bname">Core build<span class="tls-07__sr"> — weeks 3 to 7, 75% done</span></span></div>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:9/span 4;grid-row:2"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Hardening<span class="tls-07__sr"> — weeks 8 to 11, not started</span></span></div>
<p class="tls-07__lane" style="grid-row:3">Marketing</p>
<div class="tls-07__bar" style="--c:#b45309;grid-column:7/span 3;grid-row:3"><span class="tls-07__bfill" style="width:30%"></span><span class="tls-07__bname">Launch prep<span class="tls-07__sr"> — weeks 6 to 8, 30% done</span></span></div>
<div class="tls-07__bar" style="--c:#b45309;grid-column:11/span 3;grid-row:3"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Campaign<span class="tls-07__sr"> — weeks 10 to 12, not started</span></span></div>
</div>
</div>
</div>
</section>
```
## CSS
```css
.tls-07,
.tls-07 *,
.tls-07 *::before,
.tls-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-07 {
min-height: 100vh;
width: 100%;
--tl-line: #e7e5e4;
--tl-ink: #1c1917;
--tl-mut: #78716c;
--label: 110px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #fafaf9;
padding: 64px 24px;
display: grid;
place-items: center;
}
.tls-07__inner {
width: min(880px,100%);
}
.tls-07__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.tls-07__title {
font-size: clamp(20px,3.5vw,26px);
font-weight: 800;
color: var(--tl-ink);
letter-spacing: -.02em;
}
.tls-07__sub {
font-size: 12.5px;
color: var(--tl-mut);
}
.tls-07__chart {
background: #fff;
border: 1px solid var(--tl-line);
border-radius: 14px;
padding: 18px 20px 22px;
overflow: hidden;
}
.tls-07__weeks {
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
font-size: 10px;
font-weight: 700;
letter-spacing: .06em;
color: var(--tl-mut);
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid var(--tl-line);
margin-bottom: 14px;
}
.tls-07__lanes {
position: relative;
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
grid-auto-rows: 44px;
row-gap: 10px;
align-items: center;
background: repeating-linear-gradient(90deg,transparent 0,transparent calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12));
background-position: var(--label) 0;
background-repeat: no-repeat;
background-size: calc(100% - var(--label)) 100%;
background-origin: padding-box;
}
.tls-07__nowband {
grid-column: 9;
grid-row: 1/-1;
height: 100%;
background: rgb(220 38 38 / .06);
border-inline: 1px dashed rgb(220 38 38 / .4);
}
.tls-07__lane {
grid-column: 1;
font-size: 12.5px;
font-weight: 700;
color: var(--tl-mut);
padding-right: 14px;
}
.tls-07__bar {
position: relative;
height: 30px;
border-radius: 8px;
background: color-mix(in srgb,var(--c) 14%,#fff);
border: 1px solid color-mix(in srgb,var(--c) 40%,#fff);
overflow: hidden;
display: flex;
align-items: center;
}
.tls-07__bfill {
position: absolute;
inset: 0 auto 0 0;
background: color-mix(in srgb,var(--c) 26%,#fff);
}
.tls-07__bname {
position: relative;
font-size: 11px;
font-weight: 700;
color: var(--c);
padding-inline: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tls-07__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@media (max-width: 640px) {
.tls-07__weeks {
display: none;
}
.tls-07__lanes {
grid-template-columns: 1fr;
grid-auto-rows: auto;
background: none;
}
.tls-07__nowband {
display: none;
}
.tls-07__lane {
grid-row: auto!important;
padding: 6px 0 2px;
}
.tls-07__bar {
grid-column: 1!important;
grid-row: auto!important;
}
}
```
## JavaScript
```js
/* No JavaScript — every bar is grid-column: start / span duration (two integers = the whole API); week gridlines are one repeating-linear-gradient sized to the fr tracks. */
```Here's a working CSS Timeline from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Team Swimlane Gantt Timeline — Grid-Line Placement, No Libraries
Source: https://codefronts.com/layouts/css-timelines/team-swimlane-gantt-timeline-grid-line-placement-no-libraries/
A real project Gantt without a charting library: team swimlanes as grid rows, a 12-week header, and every task bar positioned purely by grid-column line numbers — change a task's start and length by editing two integers in the HTML. Week gridlines come from one repeating gradient; the current week is a highlighted column band.
## HTML
```html
<section class="tls-07" aria-label="Team gantt timeline demo">
<div class="tls-07__inner">
<header class="tls-07__head"><h3 class="tls-07__title">Q3 delivery plan</h3><p class="tls-07__sub">12 weeks · 3 teams</p></header>
<div class="tls-07__chart">
<div class="tls-07__weeks" aria-hidden="true">
<span></span><span>W1</span><span>W2</span><span>W3</span><span>W4</span><span>W5</span><span>W6</span><span>W7</span><span>W8</span><span>W9</span><span>W10</span><span>W11</span><span>W12</span>
</div>
<div class="tls-07__lanes">
<div class="tls-07__nowband" aria-hidden="true"></div>
<p class="tls-07__lane">Design</p>
<!-- grid-column: (week + 1) / span duration -->
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:2/span 3;grid-row:1"><span class="tls-07__bfill" style="width:100%"></span><span class="tls-07__bname">Discovery<span class="tls-07__sr"> — weeks 1 to 3, done</span></span></div>
<div class="tls-07__bar" style="--c:#7c3aed;grid-column:5/span 4;grid-row:1"><span class="tls-07__bfill" style="width:60%"></span><span class="tls-07__bname">Design system<span class="tls-07__sr"> — weeks 4 to 7, 60% done</span></span></div>
<p class="tls-07__lane" style="grid-row:2">Engineering</p>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:4/span 5;grid-row:2"><span class="tls-07__bfill" style="width:75%"></span><span class="tls-07__bname">Core build<span class="tls-07__sr"> — weeks 3 to 7, 75% done</span></span></div>
<div class="tls-07__bar" style="--c:#0e7490;grid-column:9/span 4;grid-row:2"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Hardening<span class="tls-07__sr"> — weeks 8 to 11, not started</span></span></div>
<p class="tls-07__lane" style="grid-row:3">Marketing</p>
<div class="tls-07__bar" style="--c:#b45309;grid-column:7/span 3;grid-row:3"><span class="tls-07__bfill" style="width:30%"></span><span class="tls-07__bname">Launch prep<span class="tls-07__sr"> — weeks 6 to 8, 30% done</span></span></div>
<div class="tls-07__bar" style="--c:#b45309;grid-column:11/span 3;grid-row:3"><span class="tls-07__bfill" style="width:0%"></span><span class="tls-07__bname">Campaign<span class="tls-07__sr"> — weeks 10 to 12, not started</span></span></div>
</div>
</div>
</div>
</section>
```
## CSS
```css
.tls-07,
.tls-07 *,
.tls-07 *::before,
.tls-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.tls-07 {
min-height: 100vh;
width: 100%;
--tl-line: #e7e5e4;
--tl-ink: #1c1917;
--tl-mut: #78716c;
--label: 110px;
font-family: 'Segoe UI',system-ui,sans-serif;
background: #fafaf9;
padding: 64px 24px;
display: grid;
place-items: center;
}
.tls-07__inner {
width: min(880px,100%);
}
.tls-07__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.tls-07__title {
font-size: clamp(20px,3.5vw,26px);
font-weight: 800;
color: var(--tl-ink);
letter-spacing: -.02em;
}
.tls-07__sub {
font-size: 12.5px;
color: var(--tl-mut);
}
.tls-07__chart {
background: #fff;
border: 1px solid var(--tl-line);
border-radius: 14px;
padding: 18px 20px 22px;
overflow: hidden;
}
.tls-07__weeks {
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
font-size: 10px;
font-weight: 700;
letter-spacing: .06em;
color: var(--tl-mut);
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid var(--tl-line);
margin-bottom: 14px;
}
.tls-07__lanes {
position: relative;
display: grid;
grid-template-columns: var(--label) repeat(12,1fr);
grid-auto-rows: 44px;
row-gap: 10px;
align-items: center;
background: repeating-linear-gradient(90deg,transparent 0,transparent calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12 - 1px),var(--tl-line) calc((100% - var(--label)) / 12));
background-position: var(--label) 0;
background-repeat: no-repeat;
background-size: calc(100% - var(--label)) 100%;
background-origin: padding-box;
}
.tls-07__nowband {
grid-column: 9;
grid-row: 1/-1;
height: 100%;
background: rgb(220 38 38 / .06);
border-inline: 1px dashed rgb(220 38 38 / .4);
}
.tls-07__lane {
grid-column: 1;
font-size: 12.5px;
font-weight: 700;
color: var(--tl-mut);
padding-right: 14px;
}
.tls-07__bar {
position: relative;
height: 30px;
border-radius: 8px;
background: color-mix(in srgb,var(--c) 14%,#fff);
border: 1px solid color-mix(in srgb,var(--c) 40%,#fff);
overflow: hidden;
display: flex;
align-items: center;
}
.tls-07__bfill {
position: absolute;
inset: 0 auto 0 0;
background: color-mix(in srgb,var(--c) 26%,#fff);
}
.tls-07__bname {
position: relative;
font-size: 11px;
font-weight: 700;
color: var(--c);
padding-inline: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tls-07__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@media (max-width: 640px) {
.tls-07__weeks {
display: none;
}
.tls-07__lanes {
grid-template-columns: 1fr;
grid-auto-rows: auto;
background: none;
}
.tls-07__nowband {
display: none;
}
.tls-07__lane {
grid-row: auto!important;
padding: 6px 0 2px;
}
.tls-07__bar {
grid-column: 1!important;
grid-row: auto!important;
}
}
```
## JavaScript
```js
/* No JavaScript — every bar is grid-column: start / span duration (two integers = the whole API); week gridlines are one repeating-linear-gradient sized to the fr tracks. */
```How this works
The chart is one CSS grid: grid-template-columns: 120px repeat(12, 1fr) — a lane-label column plus 12 week tracks. Task bars are plain divs placed with grid-column: 3 / span 4 (start week +2 because line 1 is the label column; span = duration in weeks). This is the demo's teaching point: Gantt charts are grid line arithmetic. No absolute positioning, no percentage math, no JS layout — and bars reflow perfectly when the container resizes because the tracks are fr units.
Vertical week gridlines are a single repeating-linear-gradient painted on the lanes wrapper — one hairline per track boundary, calculated as calc((100% - 120px) / 12) intervals offset past the label column. The current-week highlight is one more div spanning all rows at grid-column: 9, tinted and z-indexed beneath the bars.
Each lane is labeled with a sticky-free row header; bars carry their task name inline when wide enough, and a title attribute plus visually-hidden duration text ('Weeks 3 through 6') for assistive tech. Bar colors are per-team tokens; progress-within-task is an inner fill div at a percentage width — the only percentage in the whole chart.
Techniques used: grid-column line arithmetic for bar placement (start / span integers in markup) · repeating-linear-gradient week gridlines aligned to fr tracks · full-height current-week column band via grid-row 1/-1 · per-team color tokens · inner-div task progress fill · visually-hidden duration text for screen readers · mobile fallback collapses to a task list
Make it yours
- Reschedule anything: edit the two integers in
grid-column: start / span n— that IS the API. - Timescale: repeat(12,1fr) → repeat(30,1fr) for a monthly view of days; the gradient interval divisor must match the track count.
- Current week: the grid-column on the band div — server-render this integer for a live chart.
- Milestones: add a diamond div placed with grid-column:N/span 1 and justify-self:center, rotate:45deg.
Gotchas — read before shipping
- Grid line 1 is the LABEL column — week 1 starts at line 2. Off-by-one here shifts the whole schedule; the demo comments the mapping in the HTML.
- The gridline gradient must subtract the label column width in its calc, or lines drift off the track boundaries as the chart resizes.
- Bars in the same lane on overlapping weeks will stack into new implicit rows — that's grid doing the right thing; embrace it as automatic conflict display or split lanes.
- A 12-column Gantt at 360px is unreadable no matter what you do — ship the list-view fallback (the demo collapses below 640px) instead of shrinking bars into slivers.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 113+ | 111+ |
Floor set by color-mix() as this demo is written. The core technique itself goes back further — Chrome all.
Pure CSS grid with line placement — baseline since 2017. Nothing gated anywhere in the demo.