Ekehi, meaning "sunrise" or "discovery", is an open-source business resource platform built for women entrepreneurs in Nigeria and across Africa. The problem it solves is real: funding exists, grants exist, credit schemes and training programmes exist, but finding them is genuinely hard, especially if you are not already connected to the right people. Ekehi puts all of that in one place. A searchable, filterable directory of funding opportunities, credit products, and training resources, built mobile-first for the women who need it most.
The platform is live at ekehi.netlify.app and the full codebase is open source. Built by 15 people across four sprints. See it here: Ekehi on GitHub
This is my final sprint entry. Four weeks, three previous articles, and a lot of lessons.
The Product and Why It Exists
Ekehi is a business intelligence platform designed to bridge the gap between women entrepreneurs and limited access to capital, professional networks, and visibility. It serves as a searchable, filterable hub that connects women-led SMEs with active grants, credit schemes, and growth training, all in one place.
The stack the team was assigned to build with is Vanilla HTML, CSS, and JavaScript on the frontend with Node.js and Express.js on the backend and Supabase handling the database and authentication. For many of the women this platform serves, mobile is the primary device and connectivity is not always reliable. Every technical decision on this project sits inside that context.
The choice of purple as a primary colour was intentional. By aligning with the global identity of International Women's Day, the platform signals immediate trust and dignity to the women it is built for.
The Journey So Far
I published articles covering my work in each sprint. Here is the quick version:
Sprint 1 — Built the Mission section on the landing page. The technical challenge was positioning four decorative SVG petal icons around the eyebrow label using absolute positioning rather than flexbox, which kept them from disrupting the layout flow.
Sprint 2 — Built the filter section on the Opportunities page. This was the most layered task I worked on across the whole sprint. ES module imports, a shared component system, CSS scoping, JavaScript wiring, and a filter state object all in one task.
Sprint 3 — Refactored the What We Offer section into a two-column interactive layout with CSS Grid and JavaScript click handlers for item activation.
Each of those sprints has a full write-up. This article covers Sprint 4 and wraps up the full journey.
What I Shipped This Week
My final task was building the guide detail page at client/resources/guides/detail/. This is the reading page that renders a single guide's full content when a user navigates from the resources page.
The HTML Structure
Before writing any CSS or JavaScript, the HTML structure came first. The page has two main areas: a sidebar on the left and an article on the right. The semantic element choices were deliberate:
<main class="container">
<div class="guide">
<aside class="guide__sidebar">
<a href="/resources/" class="guide__back">← Go back</a>
<nav class="guide__toc" aria-label="Table of contents">
<div id="toc-root"></div>
</nav>
</aside>
<article class="guide__content">
<h1 class="guide__title" id="guide-title"></h1>
<div class="guide__body" id="guide-body"></div>
</article>
</div>
</main>
<aside> is the correct semantic tag for secondary content that supports the main content. <nav> inside the sidebar is correct because the table of contents is navigation within the page. <article> is correct for the main content because a guide is a self-contained piece of long-form content that makes sense on its own.
The id="toc-root" and id="guide-body" divs are empty mounting points. JavaScript fills them with content at runtime.
The Layout
The two-column layout uses CSS Grid with 250px 1fr rather than 1fr 1fr:
.guide {
display: grid;
grid-template-columns: 250px 1fr;
gap: var(--space-16);
padding-block: var(--space-10);
align-items: start;
}
The sidebar has a predictable fixed width since it just holds a list of links. The content area should take all the remaining space since it holds long-form article text. Equal columns would waste space on the sidebar and squish the content. align-items: start keeps the sidebar from stretching to match the full height of the article.
The sidebar is sticky so it stays visible as the user scrolls through the long content without moving with the page.
The Routing
There is no API endpoint for guides yet so the page uses hardcoded dummy data. The routing reads a guideId from the URL using URLSearchParams:
const params = new URLSearchParams(window.location.search);
const guideId = params.get("guideId");
A URL like /resources/guides/detail/index.html?guideId=1 gives guideId the value "1". The guides object is keyed with string keys to match. If the guideId is missing or does not match anything in the data, the page shows a friendly error state instead of breaking.
The Collapsible Table of Contents
The issue specifically required a collapsible table of contents. Each section heading is a button that toggles its sub-items open and closed using aria-expanded:
headingBtn.addEventListener("click", () => {
const isExpanded = headingBtn.getAttribute("aria-expanded") === "true";
headingBtn.setAttribute("aria-expanded", String(!isExpanded));
itemList.style.display = isExpanded ? "none" : "flex";
});
aria-expanded tells screen readers whether the section is open or closed so keyboard and assistive technology users know the state of the TOC without having to guess.
The TOC items are <button> elements rather than <span> elements. This came up as a PR review comment from GitHub Copilot. Spans are not keyboard focusable by default which means keyboard users could not interact with the TOC. Buttons are focusable natively and behave correctly without any extra work.
The active item gets a tilde prefix through CSS alone with no extra JavaScript needed:
.guide__toc-item--active::before {
content: "~ ";
}
Responsive Behaviour
On mobile the table of contents is hidden entirely and the layout collapses to a single column. The decision to hide rather than collapse the TOC was deliberate. On a small screen a long list of navigation links before the article content creates friction. The reader just wants to get to the content. The Go back link stays visible since it sits outside the TOC as a sibling element, not inside it.
Full Sprint Retrospective
What went well was the tooling and the structure. GitHub Issues, ClickUp, and the branching strategy kept the project moving even when things got messy. Having clear acceptance criteria on every issue meant you always knew what done looked like. The team leads showing up and keeping momentum going made the difference in weeks where things could have stalled.
What surprised me was how much communication, or the lack of it, can affect a whole team. People not showing up to tasks, going quiet when help is needed, and being unwilling to step outside their assigned role even when the whole team might suffer for it. That was hard to watch and harder to manage. Leadership also surprised me. It is not something you prepare for. You just have to stand up and take the lead even when others are not following, and figure it out as you go.
What I would do differently is take up more responsibility earlier. I held back in some areas where I could have stepped in and I think the team would have benefited if I had been more proactive from the start. I would also push harder on communication from week one rather than trying to manage the gaps later.
What I am most proud of is the filter section on the Opportunities page from Sprint 2. It was the most layered task I worked on across the whole sprint. ES module imports, a shared component system, CSS scoping, JavaScript wiring, and a filter state object all in one task. Working through a conflict between what the issue specified and what the codebase actually supported at the time, methodically rather than panicking, was a personal win for me.
Key Technical Lessons
CSS Grid vs Flexbox. I went into this project knowing Flexbox but not really knowing when to reach for Grid. By the end I understood it clearly. Flexbox for one-dimensional layouts, Grid for two-dimensional ones. Every two-column layout I built across the sprint used Grid because both columns needed to be aware of each other vertically.
Working within a design system. Every value traces back to a CSS custom property. No hardcoded colours or values. This discipline took getting used to but I understand now why it matters. Consistency across a codebase with 15 contributors would fall apart without it.
CSS scoping. When a component renders its own HTML at runtime through JavaScript, you cannot add classes to its internal elements directly in your HTML. You scope your styles through a parent BEM class you control. This keeps your overrides contained to your section and leaves the shared component untouched everywhere else.
Semantic HTML is not optional. Choosing the right HTML element matters. <aside> for sidebar content, <nav> for navigation lists, <article> for self-contained content, <button> for anything interactive. These choices affect accessibility, screen reader behaviour, and keyboard navigation.
Reading someone else's code before touching it. Refactoring the What We Offer section in Sprint 3 taught me to slow down before writing anything. Read first, understand the structure, then make changes. Jumping in too quickly causes more problems than it solves.
Git as a collaboration tool. Forking, upstream syncing, pull requests, merge conflicts, ES module imports. I came into this project as a beginner and the git workflow alone taught me more than I expected. One merged PR on a real open source project teaches you more than ten personal projects built alone.
What Is Next
I plan to strengthen my JavaScript fundamentals properly before moving on. After that, TypeScript is the next step. I also want to start learning how to integrate data structures and algorithms into writing better, more efficient code. This sprint showed me that knowing how to write code is one thing. Writing code that is clean, scalable, and makes sense to someone else is entirely another.
Meet The Team
Frontend Track
Marionbraide — marionbraideinfo@gmail.com
Osezele Iboi — ejemeniboi@gmail.com
Esther Orieji — oriejiesther@gmail.com
Iyobosa — omosiyobo@gmail.com
Oluchi Okwuosa — Okwuosaoluchi95@gmail.com
Victor Okoukoni — victorokoukoni@gmail.com
Florence Onwuegbuzie — florenceworkhub@gmail.com
Fatihat — fatihatulkhaer@gmail.com
UI/UX Track
Michael Babjide Boluwatife
Fisayo Rotibi
Osuji Wisdom
Mobile Track
Gabriel Abubakar — Contributing as QA
Jesse Nwaokolo — Contributing to frontend
Backend Track
Olusegun Adeleke
Sodiq Semiu
TEE Foundation | Tabi Project | International Women's Day Initiative | March 2026
Top comments (0)