4.6.x theme.inc theme_item_list($items = array(), $title = NULL)
4.7.x theme.inc theme_item_list($items = array(), $title = NULL, $type = 'ul')
5.x theme.inc theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL)
6.x theme.inc theme_item_list($items = array(), $title = NULL, $type = 'ul', $attributes = NULL)
7.x theme.inc theme_item_list($variables)

Returns HTML for a list or nested list of items.


$variables: An associative array containing:

  • items: An array of items to be displayed in the list. If an item is a string, then it is used as is. If an item is an array, then the "data" element of the array is used as the contents of the list item. If an item is an array with a "children" element, those children are displayed in a nested list. All other elements are treated as attributes of the list item element.
  • title: The title of the list.
  • type: The type of list to return (e.g. "ul", "ol").
  • attributes: The attributes applied to the list element.

Related topics

27 theme calls to theme_item_list()
aggregator_block_view in modules/aggregator/aggregator.module
Implements hook_block_view().
authorize.php in ./authorize.php
Administrative script for running authorized file operations.
book_render in modules/book/book.pages.inc
Menu callback: Prints a listing of all books.
callback_batch_finished in modules/system/form.api.php
Complete a batch process.
entity_query_access_test_sample_query in modules/simpletest/tests/entity_query_access_test.module
Returns the results from an example EntityFieldQuery.

... See full list


includes/theme.inc, line 2175
The theme system, which controls the output of Drupal.


function theme_item_list($variables) {
  $items = $variables['items'];
  $title = $variables['title'];
  $type = $variables['type'];
  $attributes = $variables['attributes'];

  // Only output the list container and title, if there are any list items.
  // Check to see whether the block title exists before adding a header.
  // Empty headers are not semantic and present accessibility challenges.
  $output = '<div class="item-list">';
  if (isset($title) && $title !== '') {
    $output .= '<h3>' . $title . '</h3>';
  if (!empty($items)) {
    $output .= "<{$type}" . drupal_attributes($attributes) . '>';
    $num_items = count($items);
    $i = 0;
    foreach ($items as $item) {
      $attributes = array();
      $children = array();
      $data = '';
      if (is_array($item)) {
        foreach ($item as $key => $value) {
          if ($key == 'data') {
            $data = $value;
          elseif ($key == 'children') {
            $children = $value;
          else {
            $attributes[$key] = $value;
      else {
        $data = $item;
      if (count($children) > 0) {

        // Render nested list.
        $data .= theme_item_list(array(
          'items' => $children,
          'title' => NULL,
          'type' => $type,
          'attributes' => $attributes,
      if ($i == 1) {
        $attributes['class'][] = 'first';
      if ($i == $num_items) {
        $attributes['class'][] = 'last';
      $output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
    $output .= "</{$type}>";
  $output .= '</div>';
  return $output;


roper.’s picture

Why are child lists rendered through the hard-coded theme_item_list() instead of theme('item_list', ...)? Same is true of D6.

moshe weitzman’s picture


lemark’s picture

Cause it is derived to this particular default item_list implementation. So all is OK.

WilliamB’s picture

From this, i found how to add attributes to the UL tag, but it's not clear to me how i can add attributes to the LI tags?

Can someone helps?

dinarcon’s picture

Hi WilliamB,

In order to add attributes to the li tags, each element in the items array ($variables['items']) must be another (associative) array. For the later, the value of the data key will be treated as the content of the li tag, while any other key (expect for children) will be treated as an attribute to be added (using the key's value as the value for the attribute). Since this might sound confusing, I wrote a simple example...

Let's say you want to print a list of authenticated users. For each of them, you want to print his or her username and add an id attribute with the user's id (uid) to the list item, as well as a class attribute with the roles the user has. Then you would have to do following:

$title = t('My custom listing');
$type = 'ul';
// The following attributes apply to the list tag (e.g., 
    ) $attributes = array( 'id' => 'my-custom-listing', 'class' => 'custom-class another-custom-class', // a string or indexed (string) array with the classes for the list tag ); $uids = retrieve_uids(); // Replace with your own function $accounts = array(); foreach ($uids as $uid) { $accounts[] = user_load($uid); } $items = array(); foreach ($accounts as $account) { $items[] = array( 'data' => $account->name, 'id' => $account->uid, // be careful not to add another id attribute on the page that might be the same as one of the uids or your page will not validate 'class' => array_keys($account->roles), // value for 'class' key MUST be an (indexed) array. Using a string value like '2 3 4' produces an error ); } theme_item_list(array('items' => $items, 'title' => $title, 'type' => $type, 'attributes' => $attributes));

The preceding code should produce an output similar to:

<div class="item-list">
  <h3>My custom listing</h3>
  <ul id="my-custom-listing" class="custom-class another-custom-class">
    <li id="1" class="2 3 4 first">admin</li>
    <li id="2" class="2 4">john</li>
    <li id="3" class="2 last">doe</li>

Note that if the function that retrieves the uids returns an empty array, the the resulting html will not have any li element. An if (!empty($uids)) could be used, but that was skipped to keep the example simple. Also, using the array key from $account->roles as classes might not be intuitive (or useful!). The array value might be more useful, but because role names might contain whitespaces, it would require additional processing if you wanted to use them as classes. At least you should replace the whitespaces with dashes.

Hopefully the example clears your doubt. Of course, you can also read the function's implementation to see how it works.

Siripong’s picture

Try this if you don't want to implement retrieve_uids();

  $items[] = array(
    'data' => '1',
    'class' => array('dummy'=>'category'),
    'children' => array('1.1','1.2',array('data'=>'1.3','class' => array('dummy'=>'category'),'children'=>array('1.3.1','1.3.2')))
samspinoy’s picture


I am wondering how to properly generate nested arrays with theme_list_item. Right now I have a nested array where every item has class attributes:

[0] -> [data] = item 1
     -> [class] = level-0
     -> [children] -> [0] -> [data] = item 2
                          -> [class] = level-1
                   -> [1] -> [data] = item 3
                          -> [class] = level-1
[1] -> [data] = item 4
    -> [class] = level-0

The problem here is that the nested ul takes on the classes of the li's that are on the same level. In this case this would give

  <li class=level-0>item 1
    <ul class=level-0>
      <li class=level-1>item 2</li>
      <li class=level-1>item 3</li>
  <li class=level-0>item4</li>

I do not want my nested ul to have any classes. Is this default behaviour with the theme_list_item function? How do I prevent this?

sirkitree’s picture

This can hang you up at times because you need to realize that you need a key called 'items'.

Succinct example:

  $items['items'] = array(
    l('Configure', 'admin/config'),
    l('Structure', 'admin/structure'),
  return theme('item_list', $items);
EvanDonovan’s picture

This is because D7 always uses $variables for second parameter of a theme function, where $variables is an array keyed by the names of the variables. This is so they can be overridden in a preprocess function.

Perhaps this makes more sense in the code if you name like so:

  $items = array(
    l('Batch history', 'admin/reports/salesforce/batch'),
    l('Currently queued items', 'admin/reports/salesforce/current'),
    l('Retry queue', 'admin/reports/salesforce/retries'),
    l('Permanent failures', 'admin/reports/salesforce/permanent-failures'),
  $output .= theme('item_list', array('items' => $items));
ninuann’s picture

Im using drupal 7, I would like to know whether i can use the same function to implement multi-level list items. As below:

If possible can anybody help me with an example?

SKAUGHT’s picture

build your list in reverse--start with the inner most and render it into a parent.

jcmiller09’s picture

but doesn't this bring in a div wrapper for the sub-UL? I am trying to use the theme function, but it makes html super messy for things like Suckerfish/superfish dropdown menus.

anwar_max’s picture

paulsheldrake’s picture

This comment has some code that would work for you

herve’s picture

On D7, to use attributes on li elements you have to use attributes array instead "class" or "id" directly. Rather, you will get a fatal error.

moniuch’s picture

I personally do not like the wrapper <div class="item-list"></div> being hard-coded. There are contexts where such a wrapper might be not desired, such as when you want to generate markup appropriate for JavaScript/jQuery plugins. All you can do now is act globally, but this can have undesired effect as well.
Therefore I would either vote for providing an additional argument to the function to control that, or provide an additional info into the $variables array supplied to the THEME_item_list which would tell me more about the context of the display.
If developers agree upon that, please try to apply such a patch to D7.x release.

letrotteur’s picture

I think this functionality could be a great improvement. The class item-list seems to be quite generic for a wrapper class. I can see use case where it would be good to have something more specific to target the pager wrapper or something like that.

kris_mcl’s picture

I wanted to have the option of adding a unique id to the wrapper. Here's how I solved it by overriding theme_item_list() in my theme's template.php.

Look for comments starting with KM: for explanation. I'm not sure this is the best way to do it, but it works nicely.

function MYTHEME_item_list($variables) {
  $items = $variables['items'];
  $title = $variables['title'];
  $type = $variables['type'];
  $attributes = $variables['attributes'];
  // KM: Check for an item in the $variables array called 'container_id'
  // KM: If not found, default to a blank string.
  $container_id = isset($variables['container_id']) ? ' id="' . $variables['container_id'] . '"' : '';

  // Only output the list container and title, if there are any list items.
  // Check to see whether the block title exists before adding a header.
  // Empty headers are not semantic and present accessibility challenges.
  // KM: Add the container_id attribute to the list element container
  $output = '
'; if (isset($title) && $title !== '') { $output .= '

' . $title . '

'; } if (!empty($items)) { $output .= "'; $num_items = count($items); foreach ($items as $i => $item) { $attributes = array(); $children = array(); $data = ''; if (is_array($item)) { foreach ($item as $key => $value) { if ($key == 'data') { $data = $value; } elseif ($key == 'children') { $children = $value; } else { $attributes[$key] = $value; } } } else { $data = $item; } if (count($children) > 0) { // Render nested list. $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes)); } if ($i == 0) { $attributes['class'][] = 'first'; } if ($i == $num_items - 1) { $attributes['class'][] = 'last'; } $output .= '
  • ' . $data . "
  • \n"; } $output .= "$type>"; } $output .= '
    '; return $output; }

    I use it like this:

    $output .= theme('item_list', array(
        'items' => $scopeList,
        'title' => $scopeName,
        'type' => 'ul',
        'attributes' => array('id' => 'scope-list'),
        'container_id' => 'scope-list-wrapper',

    Here's the output:

    <div class="item-list" id="type-list-wrapper">
      <ul id="type-list">
        <li class="first"><a href="/projects">All</a></li>
        <li><a href="/projects/residential/">Residential</a></li>
        <li class="last"><a href="/projects/commercial/" class="active">Commercial</a></li>

    I hope that helps, I'm sure you could use the same technique of passing additional items to the $variables argument of your overridden theme function to determine what type of wrapper to use, whether to use one at all, etc.

    hachesilva’s picture

    I think a much better approach would be to set an attributes array instrad of just an id value, like so:

    function MYTHEME_item_list($variables) {
      $items = $variables['items'];
      $title = $variables['title'];
      $type = $variables['type'];
      $attributes = $variables['attributes'];
      $wrapper_attributes = $variables['wrapper_attributes'];
      if ( !isset($wrapper_attributes['class']) ) {
        $wrapper_attributes['class'] = array('item-list');
      // Use drupal attributes function to convert an array into HTML attributes
      $output = '
    '; if (isset($title) && $title !== '') { $output .= '

    ' . $title . '

    '; } if (!empty($items)) { $output .= "'; $num_items = count($items); $i = 0; foreach ($items as $item) { $attributes = array(); $children = array(); $data = ''; $i++; if (is_array($item)) { foreach ($item as $key => $value) { if ($key == 'data') { $data = $value; } elseif ($key == 'children') { $children = $value; } else { $attributes[$key] = $value; } } } else { $data = $item; } if (count($children) > 0) { // Render nested list. $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes)); } if ($i == 1) { $attributes['class'][] = 'first'; } if ($i == $num_items) { $attributes['class'][] = 'last'; } $output .= '
  • ' . $data . "
  • \n"; } $output .= "$type>"; } $output .= '
    '; return $output; }

    Then, when you would like to add attributes like class, id, aria, data or anything else, you just have to do:

    return theme('item_list', array(
      'items' => $items,
      'attributes' => array('class' => array('pager', 'pagination')),
      'wrapper_attributes' => array('id' => 'pagination-wrapper', 'class' => array('class1', 'class2')),
    funana’s picture

    Thank you so much. Exactly what I needed to change pagination theming in a bootstrap based theme. (y)

    kapil.ropalekar’s picture

    How to pass an array of nodes to the theme_item_list ? I need to display 4 nodes which are being fetched from a while loop. But the $items array displays only the last node id and ignores others. Can anyone help me pls.

    jiqiang’s picture

    Hi kapil.ropalekar,

    Please refer to this example.

    $node_id_array = array(123, 234, 345, 456); // replace with your code
    $nodes = entity_load('node', $node_id_array);
    $data = array();
    foreach ($nodes as $node) {
      $node_view = entity_view('node', array($node), 'default'); // node renderable array
      $data['items'][] = drupal_render($node_view); // render it and make it to be an item 
    $data['#attributes'] = array('class' => array('node-list'));
    $output = theme('item_list', $data);
    bisw’s picture

    Small changes for attributes tag. To add attributes in ul tag, follow below code.
    $data['attributes'] = array('class' => array('node-list'));

    nikolaosinlight’s picture

    Why assume nested is a list and not replace:

    // Render nested list.
    $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));

    with the following:

    // Render nested children
    $data .= drupal_render($children);

    That way the nested list can in fact be just about anything e.g. <div>,<h3> tags, etc.. and those <div>'s can in turn contain other sub-lists or just about anything renderable.

    The only downside is that the attributes from the parent list don't get added to the sublists but if you are using CSS markup effectively this actually may be very much desired as it eliminates additional needless HTML.

    Let me know what you think. Right now I simply copy and pasted this theme into my template.php and modified this 1 line (and comment) but I think this is worthwhile enhancement and if others think so as well perhaps we can open a feature request and roll a patch.

    dpi’s picture

    Drupal 8, use render array:

    $item_list = array(
      '#theme' => 'item_list',
      '#items' => $items,
      '#title' => $heading,
    gusantor’s picture

    looking for a propper way to render just an item, after an ajax call, I didn't found

    I wanted to replace just an item (a li with an id), but seems the function doesn't have it as an option



    returns <div><ol><li>1</li></ol></div>

    so, I just replaced respectively at includes/theme.inc, theme_item_list

    $output .= "';


    $output .= "$type>";


          $output .= "';


           $output .= "$type>";

    and calling


    returns <li>1</li>

    please let me know, if there's a better way to do this, hopefully not hacking core

    thanks in advance

    ls206’s picture

    See this: https://www.drupal.org/node/1842756

    It also covers nested item lists