Lesson 4.2: Building with the shared components
Rule 2 says: use the shared components first. admin4's components.css already has cards, tables, badges, buttons,
notices, empty states, toolbars and form fields, in light and dark mode, with focus rings and forced-colors support.
In this lesson learners add a block to the administration's dashboard from their own extension and build it entirely
from those classes.
At a glance
| Course | Theme design |
| Module | Module 4: Styling the administration |
| Duration | 60 minutes (20 presenting, 40 exercise) |
| Level | Intermediate |
| Audience | Designers and developers who build administration pages |
| Prerequisites | Lesson 4.1; training_theme active; the administration in admin4 or admin4l |
Learning objectives
After this lesson, learners can:
- find a component and its states in the component table and in
components.css; - recognize the components on an admin4 page with the browser's inspector;
- add a dashboard block from an extension (
dashboard.ini, a template in the extension'sdesign/admin4/); - write administration markup with
.a4-table,.a4-badge,.a4-empty,.a4-actionsand.a4-btn, and no CSS of their own.
Trainer's notes
How to present it
Source: Styling the administration, components.
- The component table (6 minutes). Walk through the groups: page scope (
.a4-page), utilities (.a4-sr-only,.a4-muted,.a4-meta), cards (.a4-card,.a4-card-head,.a4-card-body,.a4-card-foot), tags and badges (.a4-badgewith.is-ok,.is-warn,.is-bad,.is-info,.is-accent), buttons (.a4-btn,.a4-btn-primary,.a4-btn-danger,.a4-btn-ghost, sizes), toolbars, tables (.a4-table-wraparoundtable.a4-table), the empty state.a4-empty, notices.a4-notice. - States are classes (3 minutes). A component is one class plus at most a state (
.is-*) or an element, so a module rule with one more class always wins. - On a real page (5 minutes). Open the admin4 dashboard with the inspector; find a card, a button and a table, and
their classes. Show that the dashboard's blocks come from
dashboard.ini [DashboardSettings] DashboardBlocks[], each drawn bydashboard/<identifier>.tplinside a card the dashboard provides. - An extension adds a block (6 minutes). Show the two files of the exercise. The template goes in the extension's
design/admin4/templates/dashboard/: the extension registers its design folders withDesignExtensions[], andadmin4is in the administration's chain.
Common questions
What if no component fits? First check again (the table is long). Then the CSS goes in the extension's own stylesheet with its own prefix and tokens only (lesson 4.4); a component missing for everyone is added centrally by the core, never copied into a module.
Do I need .a4-page around my block? Module pages are wrapped in .a4-page. A dashboard block sits inside the
dashboard's own markup, so it is not needed there.
Why dir="auto" on the name link? An item name may be in another script direction than the interface; lesson
5.4 explains it.
Pitfalls
- The block does not appear. The INI cache still has the old list; or the administration uses the classic design,
whose chain does not include
admin4. CheckSiteDesignof the administration siteaccess. - Copying classes from a classic template.
table.list,bglight/bgdarkbelong to older templates; new markup uses.a4-table. - A link without a name. An icon-only link needs a name (
.a4-sr-onlytext oraria-label); keep text links in this exercise.
Hands-on exercise
On your training installation, administration in admin4 or admin4l, signed in as your training administrator.
Open the dashboard and the browser's inspector. Find one element each with the classes
a4-btn,a4-badgeanda4-table(ortable.list), and note the page part it belongs to.Create
extension/training_theme/settings/dashboard.ini.append.php:[DashboardSettings] DashboardBlocks[]=training_notes [DashboardBlock_training_notes] Priority=70 NumberOfItems=5 PolicyList[]=content/read */ ?>Create the folder
extension/training_theme/design/admin4/templates/dashboard/and in ittraining_notes.tpl:<h2>'Latest in the content tree'|i18n( 'design/admin/dashboard/training_notes' )</h2> {def $training_items = fetch( 'content', 'list', hash( 'parent_node_id', ezini( 'NodeSettings', 'RootNode', 'content.ini' ), 'limit', $block.number_of_items, 'sort_by', array( 'modified', false() ) ) )} if $training_items <div class="a4-table-wrap"> <table class="a4-table"> <thead> <tr> <th scope="col">'Name'|i18n( 'design/admin/dashboard/training_notes' )</th> <th scope="col">'Type'|i18n( 'design/admin/dashboard/training_notes' )</th> </tr> </thead> <tbody> foreach $training_items as $training_item <tr> <td><a href=concat( 'content/view/full/', $training_item.node_id )|ezurl dir="auto">$training_item.name|wash</a></td> <td><span class="a4-badge">$training_item.class_name|wash</span></td> </tr> /foreach </tbody> </table> </div> else <p class="a4-empty">'Nothing published below the top of the tree yet.'|i18n( 'design/admin/dashboard/training_notes' )</p> /if <div class="a4-actions"> <a class="a4-btn" href=concat( 'content/view/full/', ezini( 'NodeSettings', 'RootNode', 'content.ini' ) )|ezurl>'Open the content tree'|i18n( 'design/admin/dashboard/training_notes' )</a> </div> undef $training_itemsCheck it:
php bin/php/eztemplatecheck.php -sadmin extension/training_theme/design/admin4/templates/dashboard/training_notes.tpl.Clear the caches:
php bin/php/ezcache.php --clear-tag=ini php bin/php/ezcache.php --clear-id=template-override php bin/php/ezcache.php --clear-tag=templateOn a Velocity lab, also
php bin/php/console exp:velocity restart.Reload the dashboard. Find the new block, below the shipped ones.
Change the badge to
<span class="a4-badge is-info">and reload. Tryis-okandis-accent.Change the button to
a4-btn a4-btn-primary, reload, and decide which of the two fits a block that is not the page's main action.Press the color mode button in the header to switch to dark mode and look at your block. Switch back.
Search your two new files for
styleand for#: there must be no inline CSS and no color.
Expected result
- Step 6: a card on the dashboard with the heading "Latest in the content tree", a striped table of five items with a badge per type, and a button "Open the content tree".
- Step 9: the block is dark with the rest of the page, with no white box, without any CSS written by the learner.
- Step 10: no match.
- The trainer checks one learner's dashboard in light and dark mode on the projector.

Quiz
- Where does an extension put the template of its dashboard block so that admin4 finds it?
- Which class wraps a data table so that it scrolls inside its card on a narrow window?
- A list is empty. Which component says so?
A.
.a4-noticeB..a4-emptyC. a plain<p>D..a4-meta - How is a badge shown as "ok" or "warning"?
- Why did the block look right in dark mode without any CSS of the learner's own?
Answers
- In
extension/<ext>/design/admin4/templates/dashboard/<identifier>.tpl; the extension registers its design folders withDesignExtensions[], and admin4 is in the administration's chain. .a4-table-wraparoundtable.a4-table(Styling the administration, components).- B. An empty list says so in
.a4-empty, never in a plain paragraph. - With a state class:
.a4-badge.is-ok,.is-warn,.is-bad,.is-info,.is-accent. - The components take their colors from tokens, and every color token has a dark value.
Further reading
- Styling the administration: the full component table, cards, tables and the code review checklist.
- The admin4 design: what admin4 provides around the components.
- Template operators in a theme: when a block needs more than a fetch.
Related pages
- Module 4: Styling the administration
- Theme design: course overview
- Trainers: start here
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.