Catalyst::ActionRole::Tabs - Add tabs to Catalyst controller actions


  package MyApp::Controller::Foo;

  use Moose::Role;
  use namespace::autoclean;

  BEGIN { extends 'Catalyst::Controller::ActionRole' }

  # view action has a tab
  sub view : Local Does(Tabs) Tab {

  # edit action has a tab
  sub edit : Local Does(Tabs) Tab {

  # update action uses same tab as edit action
  sub update : Local Does(Tabs) TabAlias(edit) {
    if ($form->result->has_errors) {
      $stash->{template} = 'edit.tt2';
    else {

  [% # Tab template %]
  [% # Assuming tab_navigation to be an array reference %]
  [% # See below under CALLBACK METHODS %]
  [% # For applicable CSS see below under SAMPLE CSS -%]
  <ul class="tabs">
  [% FOR tab IN tab_navigation %]
      <li[% IF tab.selected %] class="selected"[% END %]>
          <a href="[% tab.uri %]">[% tab.label %]</a>
  [% END %]


This module allows to add 'Tab' attributes to action endpoints, and it will automatically build a data structure suitable for rendering 'tabs' to switch between the methods that share the same tab structure.

Although this was originally built to help with making tabbed interfaces, it isn't limited to creating tabs, as it simply collects the information about the related actions. Actions are considered to be related if they share a namespace and the same captures from chained actions.

For examples of usage, please have a look in the test directory ./t and its subdirectories.




Activate Catalyst::ActionRole::Tabs for the action.



Assign a Tab to the action. The optional argument specifies the label text. Without an explicite label text the action name is used, with the first letter uppercased and the rest lowercased.



In some cases it is usefull to assign one tab to many actions. E.g. an action with a form to update some data, might be called initially as action edit where data is read from the database and filled into the form fields and then point the form's action to update, where the actual input processing happens. In case of an error, the same form (decorated with some error messages) would be shown again, but this time under .../update. With TabAlias update shows the same tab as edit:

  # action 'edit' has a Tab with label "Edit"
  sub edit : Local Does(Tabs) Tab { ... }

  # action 'update' has a Tab with label "Edit" too
  sub update : Local Does(Tabs) TabAlias(edit) { ... }


Normaly you never have to touch the following two methods. They are documented here to reveal their purpose.


BUILD is a standard Moose method, that is called at the end of the object construction process.

  • Asserts that not both Tab and TabAlias exist for the same action.

  • The TabAlias attribute is checked to have an argument.


Called before the actual action code to build the tabs for the current controller.

Makes use of Catalyst::ActionRole::ACL->can_visit() if available for the particular action and removes those tabs, whose actions are not allowed for the current user.

The final result is a hash, where the keys are the action names and the values are references to hashes that describe the actual tabs:


The action name. Same as the key of the main hash.


The tab label text.


A boolean that is true for the tab of the currently executed action.


An URI object of this tab's action.


This exists only in the tab for the current action (where selected is true) and if the action has a TabAlias attribute. Contains the actual action name.

A dump of the controller's tab hash might look like this:

    view => {
      name => "view",
      label => "View",
      selected => 1,
      uri => bless(
          do{\(my $o = "")},
    edit => {
      name => "edit",
      label => "Edit",
      selected => "",
      uri => bless(
          do{\(my $o = "")},

Compatibility notice:

In the first public release of this module query parameters from the current request were appended to the tab URIs. The idea behind it was to easily pass a session id. This turned out to be a bad idea, because all kinds of paramaters were passed to pages, where they might introduce complications. Therefore beginning with this release no query parameters are appended to the tab URIs automatically, and the desired query parameters must be passed manually in the "BUILD_TABS" callback method. See below how this can be done.



If method BUILD_TABS() exists in the controller class, it is called as

  $controller->BUILD_TABS($c, $tabs)

else the tabs hash as described in "execute" is stored at $c->stash->{tabs}.

If it exists BUILD_TABS() has to store the tabs data wherever appropriate. It can also be used to convert the incomig hash into an array with the desired order of tabs. Finally is is a place to apply further modifications to the tabs, like adding or removing tabs.

Here is an example for a BUILD_TABS(). It

  • turns tab data into an array with the desired order;

  • adds the query parameter session_id to all tab urls;

  • stores it onto the stash under the name tab_navigation:

  sub BUILD_TABS {
    my ($self, $c, $tabs) = @_;
    my (@tabs, $tab);
    my $session_id = $c->request->param('session_id');

    for (qw(browse add view edit remove)) {
      $tab = $tabs->{$_} or next;
        if $session_id;
      push @tabs, $tab;

    $c->stash->{tab_navigation} = \@tabs;


Here is some CSS that works with the template included in the synopsis. It's probably not exactly what you need, but it should give a decent starting point...

  ul.tabs {
    text-align: left;
    margin: 1em 0 1em 0;
    border-bottom: 1px solid #6c6;
    list-style-type: none;
    padding: 3px 10px 3px 10px;
  ul.tabs li {
    display: inline;
  ul.tabs li.selected {
    border-bottom: 1px solid #fff;
    background-color: #fff;
  ul.tabs li.selected a {
    background-color: #fff;
    color: #000;
    position: relative;
    top: 1px;
    padding-top: 4px;
  ul.tabs li a {
    padding: 3px 4px;
    border: 1px solid #6c6;
    background-color: #cfc;
    color: #666;
    margin-right: 0px;
    text-decoration: none;
    border-bottom: none;
  ul.tabs a:hover {
    background: #fff;


Bernhard Graf <graf(a)>,


You can find documentation for this module with the perldoc command.

    perldoc Catalyst::ActionRole::Tabs

You can also look for information at:


Inspired by and parts of code and documentation used from CatalystX::Controller::Tabs.

Catalyst::ActionRole::ACL to be the first released module to use ...

... the wonderful Catalyst::Controller::ActionRole.

And of course Catalyst.


Copyright 2009 Bernhard Graf.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See for more information.