When `[example attr1="value1" attr2 attr3="value3" attr4]` is parsed, the `$atts` parameter yields: $atts = array ( 'attr1' => 'value1', 0 => 'attr2', 'attr3' => 'value3', 1 => 'attr4' ); You can normalize this like so, then you can call `shortcode_atts` on it. if (!function_exists('normalize_empty_atts')) { function normalize_empty_atts ($atts) { foreach ($atts as $attribute => $value) { if (is_int($attribute)) { $atts[strtolower($value)] = true; unset($atts[$attribute]); } } return $atts; } } function paragraph_shortcode ($atts, $content = null) { extract(shortcode_atts( array ( 'last' => '' ), normalize_empty_atts($atts) )); return '<p class="super-p' .($last ? ' last' : '') .'">' .do_shortcode($content) .'</p>'; } add_shortcode('paragraph', 'paragraph_shortcode'); **Other notes:** - `[paragraph last]` would make `$last = true`. `[paragraph last="1"]` would make `$last = '1'`. - Attributes are case-insensitive, so `[paragraph LAST]` is equivalent to `[paragraph last]`. - Attributes cannot be purely numeric, so the `foreach` loop I created is safe against integer-indexed attributes.