Building Modular UI with DNN Containers (Before Everything Was “Components”)

Samuel HadsallSamuel Hadsall

I stopped working heavily in DNN about one to two years ago, but one thing I still appreciate about that era is how much it forced you to think about structure, ownership, and restraint.

Long before “component-driven UI” became the default language of frontend development, I was using DNN Containers to solve the same core problems we’re still dealing with today:

  • Too much framework-generated markup
  • Poor control over structure and styling
  • Global CSS and JS bleeding everywhere
  • No clean way to scope assets or behavior

This post is a look back at how I used DNN Containers to build cleaner, more modular UI patterns — and why those ideas still matter if you’re building blocks, widgets, or components today.


The Problem: DNN’s Markup Was Doing Too Much

Out of the box, DNN modules render a lot of wrapper markup. While that’s helpful for consistency, it often becomes a liability when you want:

  • Precise layout control
  • Predictable class hooks
  • Clean CSS grid systems
  • Reusable design patterns

I didn’t want to fight the framework on every page.

So instead of letting DNN decide how modules should look, I flipped the model:

Let DNN provide configuration data
I’ll handle markup and structure myself.

That’s where containers really started to shine.


Turning Module Titles into Semantic CSS Hooks

One of the first things I did was stop hardcoding classes.

Instead, I derived CSS class names directly from the module title. That gave me:

  • Human-readable, semantic hooks
  • Zero extra configuration
  • Consistency across environments

Example:

<%= ModuleControl.ModuleContext.Configuration.ModuleTitle
    .ToString()
    .ToLower()
    .Replace(" ","-")
    .Replace("/","-")
    .Replace(" - ","")
    .Replace("!", "") %>

Was this a perfect slug function?
No.

Did it work?
Absolutely.

It let me write CSS like:

.callout { … }
.page-banner { … }
.feature-grid { … }

…without adding a single setting or checkbox.


Rendering the Container Icon (Without the Noise)

DNN already stores a module icon — so I used it.

Instead of letting the platform inject it wherever it wanted, I rendered it only when it existed and exactly where I needed it.

<img src="<%= (string.IsNullOrWhiteSpace(ModuleControl.ModuleContext.Configuration.IconFile)
  ? ""
  : ModuleControl.ModuleContext.PortalSettings.HomeDirectory 
    + ModuleControl.ModuleContext.Configuration.IconFile) %>"
  class="d-block img-fluid" />

That gave editors visual context while keeping markup predictable and clean.

No broken images.
No extra wrappers.
No surprises.


Modifying the Parent Wrapper (On Purpose)

This is where things got interesting.

Rather than accepting DNN’s wrapper markup wholesale, I treated it as a layout hook — nothing more.

By accessing the parent control, I could append classes directly to the module wrapper:

<script runat="server">    
protected override void OnInit(EventArgs e) {
  try {
    HtmlGenericControl cParent = (HtmlGenericControl) this.Parent;
    cParent.Attributes["class"] += " grid-col grid-span-4 js-call-out";
  } catch (Exception ex) { 
    // intentionally silent
  }
}
</script>

Why this mattered:

  • I could build a true CSS grid system
  • Each module “declared” its layout role
  • Editors could stack modules freely
  • The grid just worked

This is how I built grid skin panes where dropping multiple modules with the same container instantly formed a structured layout — no templates, no extra configuration.

That pattern still holds up today.


Conditional CSS & JS with Client Resource Manager

This might be the most underrated part.

DNN’s Client Resource Manager let me load assets only when a container was present on the page.

<%@ Register TagPrefix="dnn"
  Namespace="DotNetNuke.Web.Client.ClientResourceManagement"
  Assembly="DotNetNuke.Web.Client" %>

The result:

  • No global CSS bloat
  • No unused JavaScript
  • Containers were truly modular
  • Pages stayed lean

This is the same philosophy we now praise in:

  • WordPress block-based enqueues
  • Elementor widget get_script_depends()
  • React code-splitting
  • Next.js dynamic imports

Different tools — same idea.


Multiple Containers, One System

I didn’t build just one container.

I built multiple containers for different roles:

  • Callouts
  • Page banners
  • Grid items
  • Feature blocks

Each one:

  • Owned its markup
  • Declared its layout responsibility
  • Loaded its own assets
  • Played nicely with others

That’s a component system — even if we didn’t call it that at the time.


Why This Still Matters

Looking back, what I was really doing was enforcing rules:

  • Structure belongs to the component
  • Layout is explicit, not accidental
  • Assets should load only when needed
  • Editors should compose, not configure

Those rules apply just as much today whether you’re building:

  • WordPress blocks
  • Elementor widgets
  • React components
  • Design systems

The tooling changes.
The principles don’t.


Final Thought

DNN forced you to understand what the platform was doing on your behalf — and how to take control when it got in the way.

That mindset carried forward into everything I build now.

If this sounds familiar, it’s because you’re probably already doing the same thing — just with different names and better tooling.

Sometimes the old patterns were right — we just needed better APIs.

Back to top