diff -r a66e98d03a49 django/contrib/admin/media/css/base.css
--- a/django/contrib/admin/media/css/base.css	Thu Jun 02 19:50:48 2011 +0000
+++ b/django/contrib/admin/media/css/base.css	Fri Jun 03 00:51:34 2011 +0100
@@ -351,7 +351,27 @@
     background-color: white;
     border: 1px solid #ddd;
     z-index: 2000; /* more than filters on right */
-    padding-right: 10px;
+}
+
+#sorting-popup-div table {
+    border-right: 0px;
+    border-left: 0px;
+}
+
+#sorting-popup-div .reset {
+    text-align: center;
+}
+
+#sorting-popup-div .cancel {
+    font-size: 10px;
+    background: #e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x;
+    border-top: 1px solid #ddd;
+    text-align: center;
+}
+
+#sorting-popup-div .cancel a {
+    width: 100%;
+    display: block;
 }
 
 /* ORDERABLE TABLES */
diff -r a66e98d03a49 django/contrib/admin/templates/admin/change_list_results.html
--- a/django/contrib/admin/templates/admin/change_list_results.html	Thu Jun 02 19:50:48 2011 +0000
+++ b/django/contrib/admin/templates/admin/change_list_results.html	Fri Jun 03 00:51:34 2011 +0100
@@ -12,7 +12,7 @@
 <tr>
 {% for header in result_headers %}
 <th scope="col" {{ header.class_attrib }}>
-  {% if header.sortable %}<a href="{{ header.url }}">{% endif %}
+  {% if header.sortable %}<a href="{{ header.url_primary }}">{% endif %}
   <span class="text">{{ header.text|capfirst }}</span>
   {% if header.sortable %}
     {% if header.sort_pos > 0 %}<span class="sortpos">
@@ -37,47 +37,77 @@
 
 {# Sorting popup: #}
 <div style="display: none;" id="sorting-popup-div">
-<p>{% trans "Sorting by:" %}</p>
-<ol>
-{% for header in result_headers|dictsort:"sort_pos" %}
-  {% if header.sort_pos > 0 %}
-    {% if header.ascending %}
-      <li>{% blocktrans with fieldname=header.text %}{{ fieldname }} (ascending){% endblocktrans %}</li>
-    {% else %}
-      <li>{% blocktrans with fieldname=header.text %}{{ fieldname }} (descending){% endblocktrans %}</li>
+<table>
+  <caption>
+   {% trans "Sorting by:" %}
+  </caption>
+  <tbody>
+  {% for header in result_headers|dictsort:"sort_pos" %}
+    {% if header.sort_pos > 0 %}
+    <tr>
+      <td>{{ header.sort_pos }}</td>
+      <td>{{ header.text|capfirst }}</td>
+      <td>{% if header.ascending %}{% trans "ascending" %}{% else %}{% trans "descending" %}{% endif %}</td>
+      <td><a href="{{ header.url_toggle }}">{% trans "toggle" %}</a></td>
+      <td><a href="{{ header.url_remove }}">{% trans "remove" %}</a></td>
+    </tr>
     {% endif %}
-  {% endif %}
-{% endfor %}
-</ol>
-<p><a href="{{ reset_sorting_url }}">{% trans "Reset sorting" %}</a></p>
+  {% endfor %}
+  </tbody>
+</table>
+<div class="reset"><a href="{{ reset_sorting_url }}">{% trans "Reset sorting" %}</a></div>
+<div class="cancel"><a href="javascript:void" id="sorting-popup-dismiss">{% trans "Cancel" %}</a></div>
 </div>
 <script type="text/javascript">
 <!--
 (function($) {
     $(document).ready(function() {
         var popup = $('#sorting-popup-div');
+        var img = $('#primary-sort-icon');
         /* These next lines seems necessary to prime the popup: */
         popup.offset({left:-1000, top:0});
         popup.show();
         var popupWidth = popup.width();
         popup.hide();
 
-        $('#primary-sort-icon').toggle(function(ev) {
-                                          ev.preventDefault();
-                                          var img = $(this);
-                                          var pos = img.offset();
-                                          pos.top += img.height();
-                                          if (pos.left + popupWidth >
-                                              $(window).width()) {
-                                              pos.left -= popupWidth;
-                                          }
-                                          popup.show();
-                                          popup.offset(pos);
-                                      },
-                                      function(ev) {
-                                          ev.preventDefault();
-                                          popup.hide();
-                                      });
+        var visible = false;
+
+        var escHandler = function(ev) {
+            if (ev.which == 27) {
+                hidePopup();
+                ev.preventDefault();
+            }
+        };
+
+        var showPopup = function() {
+            var pos = img.offset();
+            pos.top += img.height();
+            if (pos.left + popupWidth >
+                $(window).width()) {
+                pos.left -= popupWidth;
+            }
+            popup.show();
+            popup.offset(pos);
+            visible = true;
+            $(document).bind('keyup', escHandler);
+        };
+
+        var hidePopup = function() {
+            popup.hide();
+            visible = false;
+            $(document).unbind('keyup', escHandler);
+        };
+
+        $('#primary-sort-icon').click(function(ev) {
+            ev.preventDefault();
+            if (visible) {
+                hidePopup();
+            } else {
+                showPopup();
+            }
+        });
+
+        $('#sorting-popup-dismiss').click(hidePopup);
     });
 })(django.jQuery);
 //-->
diff -r a66e98d03a49 django/contrib/admin/templatetags/admin_list.py
--- a/django/contrib/admin/templatetags/admin_list.py	Thu Jun 02 19:50:48 2011 +0000
+++ b/django/contrib/admin/templatetags/admin_list.py	Fri Jun 03 00:51:34 2011 +0100
@@ -138,7 +138,9 @@
             new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type]
 
         # build new ordering param
-        o_list = []
+        o_list_primary = [] # URL for making this field the primary sort
+        o_list_remove  = [] # URL for removing this field from sort
+        o_list_toggle  = [] # URL for toggling order type for this field
         make_qs_param = lambda t, n: ('-' if t == 'desc' else '') + str(n)
 
         for f, ot in ordering_fields.items():
@@ -148,24 +150,30 @@
                 continue
 
             if f == ordering_field_name:
+                param = make_qs_param(new_order_type, colnum)
                 # We want clicking on this header to bring the ordering to the
-                # front
-                o_list.insert(0, make_qs_param(new_order_type, colnum))
+                # front, so use insert for o_list_primary
+                o_list_primary.insert(0, param)
+                o_list_toggle.append(param)
+                # o_list_remove - omit
             else:
-                o_list.append(make_qs_param(ot, colnum))
+                param = make_qs_param(ot, colnum)
+                o_list_primary.append(param)
+                o_list_toggle.append(param)
+                o_list_remove.append(param)
 
         if ordering_field_name not in ordering_fields:
             colnum = list_display_info[ordering_field_name]['index']
-            o_list.insert(0, make_qs_param(new_order_type, colnum))
-
-        o_list = '.'.join(o_list)
+            o_list_primary.insert(0, make_qs_param(new_order_type, colnum))
 
         yield {
             "text": info['text'],
             "sortable": True,
             "ascending": order_type == "asc",
             "sort_pos": sort_pos,
-            "url": cl.get_query_string({ORDER_VAR: o_list}),
+            "url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}),
+            "url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}),
+            "url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}),
             "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')
         }
 
