瀏覽代碼

Fixed #32018 -- Extracted admin colors into CSS variables.

Defined all colors used in the admin CSS as variables. Implemented the
following standardizations and accessibility improvements while at it:

- Improved the contrast of text to not use ratios of less than 3:1 anymore.
- Most hover states already used desaturated and darkened colors.
  Changed object tools to follow the same rule instead of showing the
  primary color on hover.

Various places used similar colors; those have been merged with the goal
of reducing the count of CSS variables. Contrasts have been improved in
a few places.

- Many borders used slightly different colors (e.g. #eaeaea vs. #eee)
- Help texts used #999, this has been changed to --body-quiet-color
  (#666) which has a better contrast.

Introduced fast color transitions on links and buttons.
Matthias Kestenholz 4 年之前
父節點
當前提交
0a802233ec

+ 19 - 16
django/contrib/admin/static/admin/css/autocomplete.css

@@ -14,7 +14,7 @@ select.admin-autocomplete {
 
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection,
 .select2-container--admin-autocomplete.select2-container--open .select2-selection {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
     min-height: 30px;
 }
 
@@ -29,13 +29,13 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-selection--single {
-    background-color: #fff;
-    border: 1px solid #ccc;
+    background-color: var(--body-bg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
 }
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
-    color: #444;
+    color: var(--body-fg);
     line-height: 30px;
 }
 
@@ -46,7 +46,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 .select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
@@ -95,7 +95,7 @@ select.admin-autocomplete {
 
 .select2-container--admin-autocomplete .select2-selection--multiple {
     background-color: white;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     cursor: text;
 }
@@ -115,7 +115,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
-    color: #999;
+    color: var(--body-quiet-color);
     margin-top: 5px;
     float: left;
 }
@@ -131,7 +131,7 @@ select.admin-autocomplete {
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
     background-color: #e4e4e4;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     cursor: default;
     float: left;
@@ -141,7 +141,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
-    color: #999;
+    color: var(--body-quiet-color);
     cursor: pointer;
     display: inline-block;
     font-weight: bold;
@@ -149,7 +149,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
-    color: #333;
+    color: var(--body-fg);
 }
 
 .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
@@ -167,7 +167,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
-    border: solid #999 1px;
+    border: solid var(--body-quiet-color) 1px;
     outline: 0;
 }
 
@@ -191,7 +191,7 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
 }
 
 .select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
@@ -205,6 +205,8 @@ select.admin-autocomplete {
 .select2-container--admin-autocomplete .select2-results > .select2-results__options {
     max-height: 200px;
     overflow-y: auto;
+    color: var(--body-fg);
+    background: var(--body-bg);
 }
 
 .select2-container--admin-autocomplete .select2-results__option[role=group] {
@@ -212,11 +214,12 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 .select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
-    background-color: #ddd;
+    background-color: var(--selected-bg);
+    color: var(--body-fg);
 }
 
 .select2-container--admin-autocomplete .select2-results__option .select2-results__option {
@@ -253,8 +256,8 @@ select.admin-autocomplete {
 }
 
 .select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
-    background-color: #79aec8;
-    color: white;
+    background-color: var(--primary);
+    color: var(--body-bg);
 }
 
 .select2-container--admin-autocomplete .select2-results__group {

+ 135 - 79
django/contrib/admin/static/admin/css/base.css

@@ -4,6 +4,58 @@
 
 @import url(fonts.css);
 
+/* VARIABLE DEFINITIONS */
+:root {
+  --primary: #79aec8;
+  --secondary: #417690;
+  --accent: #f5dd5d;
+
+  --body-fg: #333;
+  --body-bg: #fff;
+  --body-quiet-color: #666;
+  --body-loud-color: #000;
+
+  --header-color: #ffc;
+  --header-branding-color: var(--accent);
+  --header-bg: var(--secondary);
+  --header-link-color: #fff;
+
+  --breadcrumbs-fg: #c4dce8;
+  --breadcrumbs-link-fg: var(--body-bg);
+  --breadcrumbs-bg: var(--primary);
+
+  --link-fg: #447e9b;
+  --link-hover-color: #036;
+  --link-selected-fg: #5b80b2;
+
+  --hairline-color: #e8e8e8;
+  --border-color: #ccc;
+
+  --error-fg: #ba2121;
+
+  --message-success-bg: #dfd;
+  --message-warning-bg: #ffc;
+  --message-error-bg: #ffefef;
+
+  --darkened-bg: #f8f8f8; /* A bit darker than --body-bg */
+  --selected-bg: #e4e4e4; /* E.g. selected table cells */
+  --selected-row: #ffc;
+
+  --button-fg: #fff;
+  --button-bg: var(--primary);
+  --button-hover-bg: #609ab6;
+  --default-button-bg: var(--secondary);
+  --default-button-hover-bg: #205067;
+  --close-button-bg: #888; /* Previously #bbb, contrast 1.92 */
+  --close-button-hover-bg: #747474;
+  --delete-button-bg: #ba2121;
+  --delete-button-hover-bg: #a41515;
+
+  --object-tools-fg: var(--button-fg);
+  --object-tools-bg: var(--close-button-bg);
+  --object-tools-hover-bg: var(--close-button-hover-bg);
+}
+
 html, body {
     height: 100%;
 }
@@ -13,19 +65,20 @@ body {
     padding: 0;
     font-size: 14px;
     font-family: "Roboto","Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif;
-    color: #333;
-    background: #fff;
+    color: var(--body-fg);
+    background: var(--body-bg);
 }
 
 /* LINKS */
 
 a:link, a:visited {
-    color: #447e9b;
+    color: var(--link-fg);
     text-decoration: none;
+    transition: color 0.15s, background 0.15s;
 }
 
 a:focus, a:hover {
-    color: #036;
+    color: var(--link-hover-color);
 }
 
 a:focus {
@@ -37,7 +90,7 @@ a img {
 }
 
 a.section:link, a.section:visited {
-    color: #fff;
+    color: var(--body-bg);
     text-decoration: none;
 }
 
@@ -64,7 +117,7 @@ h1 {
     margin: 0 0 20px;
     font-weight: 300;
     font-size: 20px;
-    color: #666;
+    color: var(--body-quiet-color);
 }
 
 h2 {
@@ -80,7 +133,7 @@ h2.subhead {
 h3 {
     font-size: 14px;
     margin: .8em 0 .3em 0;
-    color: #666;
+    color: var(--body-quiet-color);
     font-weight: bold;
 }
 
@@ -93,7 +146,7 @@ h4 {
 h5 {
     font-size: 10px;
     margin: 1.5em 0 .5em 0;
-    color: #666;
+    color: var(--body-quiet-color);
     text-transform: uppercase;
     letter-spacing: 1px;
 }
@@ -131,7 +184,7 @@ fieldset {
     min-width: 0;
     padding: 0;
     border: none;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
 }
 
 blockquote {
@@ -144,7 +197,7 @@ blockquote {
 
 code, pre {
     font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace;
-    color: #666;
+    color: var(--body-quiet-color);
     font-size: 12px;
     overflow-x: auto;
 }
@@ -161,8 +214,8 @@ code strong {
 
 hr {
     clear: both;
-    color: #eee;
-    background-color: #eee;
+    color: var(--hairline-color);
+    background-color: var(--hairline-color);
     height: 1px;
     border: none;
     margin: 0;
@@ -183,7 +236,7 @@ hr {
 
 .help, p.help, form p.help, div.help, form div.help, div.help li {
     font-size: 11px;
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 div.help ul {
@@ -199,7 +252,7 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
 }
 
 .quiet, a.quiet:link, a.quiet:visited {
-    color: #999;
+    color: var(--body-quiet-color);
     font-weight: normal;
 }
 
@@ -219,13 +272,13 @@ p img, h1 img, h2 img, h3 img, h4 img, td img {
 
 table {
     border-collapse: collapse;
-    border-color: #ccc;
+    border-color: var(--border-color);
 }
 
 td, th {
     font-size: 13px;
     line-height: 16px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
     vertical-align: top;
     padding: 8px;
 }
@@ -237,37 +290,37 @@ th {
 
 thead th,
 tfoot td {
-    color: #666;
+    color: var(--body-quiet-color);
     padding: 5px 10px;
     font-size: 11px;
-    background: #fff;
+    background: var(--body-bg);
     border: none;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 tfoot td {
     border-bottom: none;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
 }
 
 thead th.required {
-    color: #000;
+    color: var(--body-loud-color);
 }
 
 tr.alt {
-    background: #f6f6f6;
+    background: var(--darkened-bg);
 }
 
 tr:nth-child(odd), .row-form-errors {
-    background: #fff;
+    background: var(--body-bg);
 }
 
 tr:nth-child(even),
 tr:nth-child(even) .errorlist,
 tr:nth-child(odd) + .row-form-errors,
 tr:nth-child(odd) + .row-form-errors .errorlist {
-    background: #f9f9f9;
+    background: var(--darkened-bg);
 }
 
 /* SORTABLE TABLES */
@@ -276,15 +329,15 @@ thead th {
     padding: 5px 10px;
     line-height: normal;
     text-transform: uppercase;
-    background: #f6f6f6;
+    background: var(--darkened-bg);
 }
 
 thead th a:link, thead th a:visited {
-    color: #666;
+    color: var(--body-quiet-color);
 }
 
 thead th.sorted {
-    background: #eee;
+    background: var(--selected-bg);
 }
 
 thead th.sorted .text {
@@ -303,7 +356,7 @@ table thead th .text a {
 }
 
 table thead th .text a:focus, table thead th .text a:hover {
-    background: #eee;
+    background: var(--selected-bg);
 }
 
 thead th.sorted a.sortremove {
@@ -350,12 +403,12 @@ table thead th.sorted .sortoptions a.sortremove:after {
     left: 3px;
     font-weight: 200;
     font-size: 18px;
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 table thead th.sorted .sortoptions a.sortremove:focus:after,
 table thead th.sorted .sortoptions a.sortremove:hover:after {
-    color: #447e9b;
+    color: var(--link-fg);
 }
 
 table thead th.sorted .sortoptions a.sortremove:focus,
@@ -402,16 +455,18 @@ textarea {
 
 input[type=text], input[type=password], input[type=email], input[type=url],
 input[type=number], input[type=tel], textarea, select, .vTextField {
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     padding: 5px 6px;
     margin-top: 0;
+    color: var(--body-fg);
+    background-color: var(--body-bg);
 }
 
 input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
 input[type=url]:focus, input[type=number]:focus, input[type=tel]:focus,
 textarea:focus, select:focus, .vTextField:focus {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 
 select {
@@ -427,12 +482,13 @@ select[multiple] {
 /* FORM BUTTONS */
 
 .button, input[type=submit], input[type=button], .submit-row input, a.button {
-    background: #79aec8;
+    background: var(--button-bg);
     padding: 10px 15px;
     border: none;
     border-radius: 4px;
-    color: #fff;
+    color: var(--button-fg);
     cursor: pointer;
+    transition: background 0.15s;
 }
 
 a.button {
@@ -442,7 +498,7 @@ a.button {
 .button:active, input[type=submit]:active, input[type=button]:active,
 .button:focus, input[type=submit]:focus, input[type=button]:focus,
 .button:hover, input[type=submit]:hover, input[type=button]:hover {
-    background: #609ab6;
+    background: var(--button-hover-bg);
 }
 
 .button[disabled], input[type=submit][disabled], input[type=button][disabled] {
@@ -453,13 +509,13 @@ a.button {
     float: right;
     border: none;
     font-weight: 400;
-    background: #417690;
+    background: var(--default-button-bg);
 }
 
 .button.default:active, input[type=submit].default:active,
 .button.default:focus, input[type=submit].default:focus,
 .button.default:hover, input[type=submit].default:hover {
-    background: #205067;
+    background: var(--default-button-hover-bg);
 }
 
 .button[disabled].default,
@@ -474,7 +530,7 @@ input[type=button][disabled].default {
 .module {
     border: none;
     margin-bottom: 30px;
-    background: #fff;
+    background: var(--body-bg);
 }
 
 .module p, .module ul, .module h3, .module h4, .module dl, .module pre {
@@ -500,8 +556,8 @@ input[type=button][disabled].default {
     font-weight: 400;
     font-size: 13px;
     text-align: left;
-    background: #79aec8;
-    color: #fff;
+    background: var(--primary);
+    color: var(--body-bg);
 }
 
 .module caption,
@@ -528,18 +584,18 @@ ul.messagelist li {
     font-size: 13px;
     padding: 10px 10px 10px 65px;
     margin: 0 0 10px 0;
-    background: #dfd url(../img/icon-yes.svg) 40px 12px no-repeat;
+    background: var(--message-success-bg) url(../img/icon-yes.svg) 40px 12px no-repeat;
     background-size: 16px auto;
-    color: #333;
+    color: var(--body-fg);
 }
 
 ul.messagelist li.warning {
-    background: #ffc url(../img/icon-alert.svg) 40px 14px no-repeat;
+    background: var(--message-warning-bg) url(../img/icon-alert.svg) 40px 14px no-repeat;
     background-size: 14px auto;
 }
 
 ul.messagelist li.error {
-    background: #ffefef url(../img/icon-no.svg) 40px 12px no-repeat;
+    background: var(--message-error-bg) url(../img/icon-no.svg) 40px 12px no-repeat;
     background-size: 16px auto;
 }
 
@@ -549,10 +605,10 @@ ul.messagelist li.error {
     display: block;
     padding: 10px 12px;
     margin: 0 0 10px 0;
-    color: #ba2121;
-    border: 1px solid #ba2121;
+    color: var(--error-fg);
+    border: 1px solid var(--error-fg);
     border-radius: 4px;
-    background-color: #fff;
+    background-color: var(--body-bg);
     background-position: 5px 12px;
     overflow-wrap: break-word;
 }
@@ -560,8 +616,8 @@ ul.messagelist li.error {
 ul.errorlist {
     margin: 0 0 4px;
     padding: 0;
-    color: #ba2121;
-    background: #fff;
+    color: var(--error-fg);
+    background: var(--body-bg);
 }
 
 ul.errorlist li {
@@ -592,7 +648,7 @@ td ul.errorlist li {
 .form-row.errors {
     margin: 0;
     border: none;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
     background: none;
 }
 
@@ -602,7 +658,7 @@ td ul.errorlist li {
 
 .errors input, .errors select, .errors textarea,
 td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
-    border: 1px solid #ba2121;
+    border: 1px solid var(--error-fg);
 }
 
 .description {
@@ -613,19 +669,19 @@ td ul.errorlist + input, td ul.errorlist + select, td ul.errorlist + textarea {
 /* BREADCRUMBS */
 
 div.breadcrumbs {
-    background: #79aec8;
+    background: var(--breadcrumbs-bg);
     padding: 10px 40px;
     border: none;
-    color: #c4dce8;
+    color: var(--breadcrumbs-fg);
     text-align: left;
 }
 
 div.breadcrumbs a {
-    color: #fff;
+    color: var(--breadcrumbs-link-fg);
 }
 
 div.breadcrumbs a:focus, div.breadcrumbs a:hover {
-    color: #c4dce8;
+    color: var(--breadcrumbs-fg);
 }
 
 /* ACTION ICONS */
@@ -651,11 +707,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover {
 }
 
 a.deletelink:link, a.deletelink:visited {
-    color: #CC3434;
+    color: #CC3434; /* XXX Probably unused? */
 }
 
 a.deletelink:focus, a.deletelink:hover {
-    color: #993333;
+    color: #993333; /* XXX Probably unused? */
     text-decoration: none;
 }
 
@@ -685,16 +741,16 @@ a.deletelink:focus, a.deletelink:hover {
     display: block;
     float: left;
     padding: 3px 12px;
-    background: #999;
+    background: var(--object-tools-bg);
+    color: var(--object-tools-fg);
     font-weight: 400;
     font-size: 11px;
     text-transform: uppercase;
     letter-spacing: 0.5px;
-    color: #fff;
 }
 
 .object-tools a:focus, .object-tools a:hover {
-    background-color: #417690;
+    background-color: var(--object-tools-hover-bg);
 }
 
 .object-tools a:focus{
@@ -809,13 +865,13 @@ table#change-history tbody th {
     justify-content: space-between;
     align-items: center;
     padding: 10px 40px;
-    background: #417690;
-    color: #ffc;
+    background: var(--header-bg);
+    color: var(--header-color);
     overflow: hidden;
 }
 
 #header a:link, #header a:visited {
-    color: #fff;
+    color: var(--header-link-color);
 }
 
 #header a:focus , #header a:hover {
@@ -831,11 +887,11 @@ table#change-history tbody th {
     margin: 0 20px 0 0;
     font-weight: 300;
     font-size: 24px;
-    color: #f5dd5d;
+    color: var(--accent);
 }
 
 #branding h1, #branding h1 a:link, #branding h1 a:visited {
-    color: #f5dd5d;
+    color: var(--accent);
 }
 
 #branding h2 {
@@ -843,7 +899,7 @@ table#change-history tbody th {
     font-size: 14px;
     margin: -8px 0 8px 0;
     font-weight: normal;
-    color: #ffc;
+    color: var(--header-color);
 }
 
 #branding a:hover {
@@ -867,14 +923,14 @@ table#change-history tbody th {
 
 #user-tools a:focus, #user-tools a:hover {
     text-decoration: none;
-    border-bottom-color: #79aec8;
-    color: #79aec8;
+    border-bottom-color: var(--primary);
+    color: var(--primary);
 }
 
 /* SIDEBAR */
 
 #content-related {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
 }
 
 #content-related .module {
@@ -882,7 +938,7 @@ table#change-history tbody th {
 }
 
 #content-related h3 {
-    color: #666;
+    color: var(--body-quiet-color);
     padding: 0 16px;
     margin: 0 0 16px;
 }
@@ -911,22 +967,22 @@ table#change-history tbody th {
     background: none;
     padding: 16px;
     margin-bottom: 16px;
-    border-bottom: 1px solid #eaeaea;
+    border-bottom: 1px solid var(--hairline-color);
     font-size: 18px;
-    color: #333;
+    color: var(--body-fg);
 }
 
 .delete-confirmation form input[type="submit"] {
-    background: #ba2121;
+    background: var(--delete-button-bg);
     border-radius: 4px;
     padding: 10px 15px;
-    color: #fff;
+    color: var(--button-fg);
 }
 
 .delete-confirmation form input[type="submit"]:active,
 .delete-confirmation form input[type="submit"]:focus,
 .delete-confirmation form input[type="submit"]:hover {
-    background: #a41515;
+    background: var(--delete-button-hover-bg);
 }
 
 .delete-confirmation form .cancel-link {
@@ -934,17 +990,17 @@ table#change-history tbody th {
     vertical-align: middle;
     height: 15px;
     line-height: 15px;
-    background: #ddd;
     border-radius: 4px;
     padding: 10px 15px;
-    color: #333;
+    color: var(--button-fg);
+    background: var(--close-button-bg);
     margin: 0 0 0 10px;
 }
 
 .delete-confirmation form .cancel-link:active,
 .delete-confirmation form .cancel-link:focus,
 .delete-confirmation form .cancel-link:hover {
-    background: #ccc;
+    background: var(--close-button-hover-bg);
 }
 
 /* POPUP */

+ 44 - 44
django/contrib/admin/static/admin/css/changelists.css

@@ -40,13 +40,13 @@
 }
 
 #changelist .toplinks {
-    border-bottom: 1px solid #ddd;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 #changelist .paginator {
-    color: #666;
-    border-bottom: 1px solid #eee;
-    background: #fff;
+    color: var(--body-quiet-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--body-bg);
     overflow: hidden;
 }
 
@@ -68,7 +68,7 @@
 }
 
 #changelist table tfoot {
-    color: #666;
+    color: var(--body-quiet-color);
 }
 
 /* TOOLBAR */
@@ -76,22 +76,22 @@
 #toolbar {
     padding: 8px 10px;
     margin-bottom: 15px;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
-    background: #f8f8f8;
-    color: #666;
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
 }
 
 #toolbar form input {
     border-radius: 4px;
     font-size: 14px;
     padding: 5px;
-    color: #333;
+    color: var(--body-fg);
 }
 
 #toolbar #searchbar {
     height: 19px;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     padding: 2px 5px;
     margin: 0;
     vertical-align: top;
@@ -100,24 +100,24 @@
 }
 
 #toolbar #searchbar:focus {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 
 #toolbar form input[type="submit"] {
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     font-size: 13px;
     padding: 4px 8px;
     margin: 0;
     vertical-align: middle;
-    background: #fff;
+    background: var(--body-bg);
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     cursor: pointer;
-    color: #333;
+    color: var(--body-fg);
 }
 
 #toolbar form input[type="submit"]:focus,
 #toolbar form input[type="submit"]:hover {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 
 #changelist-search img {
@@ -130,7 +130,7 @@
 #changelist-filter {
     order: 1;
     width: 240px;
-    background: #f8f8f8;
+    background: var(--darkened-bg);
     border-left: none;
     margin: 0 0 0 30px;
 }
@@ -153,7 +153,7 @@
 #changelist-filter ul {
     margin: 5px 0;
     padding: 0 15px 15px;
-    border-bottom: 1px solid #eaeaea;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 #changelist-filter ul:last-child {
@@ -168,31 +168,31 @@
 
 #changelist-filter a {
     display: block;
-    color: #999;
+    color: var(--body-quiet-color);
     text-overflow: ellipsis;
     overflow-x: hidden;
 }
 
 #changelist-filter li.selected {
-    border-left: 5px solid #eaeaea;
+    border-left: 5px solid var(--hairline-color);
     padding-left: 10px;
     margin-left: -15px;
 }
 
 #changelist-filter li.selected a {
-    color: #5b80b2;
+    color: var(--link-selected-fg);
 }
 
 #changelist-filter a:focus, #changelist-filter a:hover,
 #changelist-filter li.selected a:focus,
 #changelist-filter li.selected a:hover {
-    color: #036;
+    color: var(--link-hover-color);
 }
 
 #changelist-filter #changelist-filter-clear a {
     font-size: 13px;
     padding-bottom: 10px;
-    border-bottom: 1px solid #eaeaea;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 /* DATE DRILLDOWN */
@@ -213,12 +213,12 @@
 }
 
 .change-list ul.toplinks .date-back a {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 .change-list ul.toplinks .date-back a:focus,
 .change-list ul.toplinks .date-back a:hover {
-    color: #036;
+    color: var(--link-hover-color);
 }
 
 /* PAGINATOR */
@@ -229,26 +229,26 @@
     padding-bottom: 10px;
     line-height: 22px;
     margin: 0;
-    border-top: 1px solid #ddd;
+    border-top: 1px solid var(--hairline-color);
     width: 100%;
 }
 
 .paginator a:link, .paginator a:visited {
     padding: 2px 6px;
-    background: #79aec8;
+    background: var(--primary);
     text-decoration: none;
-    color: #fff;
+    color: var(--body-bg);
 }
 
 .paginator a.showall {
     border: none;
     background: none;
-    color: #5b80b2;
+    color: var(--link-fg);
 }
 
 .paginator a.showall:focus, .paginator a.showall:hover {
     background: none;
-    color: #036;
+    color: var(--link-hover-color);
 }
 
 .paginator .end {
@@ -264,7 +264,7 @@
 
 .paginator a:focus, .paginator a:hover {
     color: white;
-    background: #036;
+    background: var(--link-hover-color);
 }
 
 /* ACTIONS */
@@ -279,22 +279,22 @@
 }
 
 #changelist table tbody tr.selected {
-    background-color: #FFFFCC;
+    background-color: var(--selected-row);
 }
 
 #changelist .actions {
     padding: 10px;
-    background: #fff;
+    background: var(--body-bg);
     border-top: none;
     border-bottom: none;
     line-height: 24px;
-    color: #999;
+    color: var(--body-quiet-color);
     width: 100%;
 }
 
-#changelist .actions.selected {
-    background: #fffccf;
-    border-top: 1px solid #fffee8;
+#changelist .actions.selected { /* XXX Probably unused? */
+    background: var(--body-bg);
+    border-top: 1px solid var(--body-bg);
     border-bottom: 1px solid #edecd6;
 }
 
@@ -314,8 +314,8 @@
     vertical-align: top;
     height: 24px;
     background: none;
-    color: #000;
-    border: 1px solid #ccc;
+    color: var(--body-fg);
+    border: 1px solid var(--border-color);
     border-radius: 4px;
     font-size: 14px;
     padding: 0 0 0 4px;
@@ -324,7 +324,7 @@
 }
 
 #changelist .actions select:focus {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }
 
 #changelist .actions label {
@@ -335,18 +335,18 @@
 
 #changelist .actions .button {
     font-size: 13px;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px;
-    background: #fff;
+    background: var(--body-bg);
     box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
     cursor: pointer;
     height: 24px;
     line-height: 1;
     padding: 4px 8px;
     margin: 0;
-    color: #333;
+    color: var(--body-fg);
 }
 
 #changelist .actions .button:focus, #changelist .actions .button:hover {
-    border-color: #999;
+    border-color: var(--body-quiet-color);
 }

+ 27 - 27
django/contrib/admin/static/admin/css/forms.css

@@ -6,7 +6,7 @@
     overflow: hidden;
     padding: 10px;
     font-size: 13px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 .form-row img, .form-row input {
@@ -26,13 +26,13 @@ form .form-row p {
 
 label {
     font-weight: normal;
-    color: #666;
+    color: var(--body-quiet-color);
     font-size: 13px;
 }
 
 .required label, label.required {
     font-weight: bold;
-    color: #333;
+    color: var(--body-fg);
 }
 
 /* RADIO BUTTONS */
@@ -215,24 +215,24 @@ fieldset.collapsed h2, fieldset.collapsed {
 }
 
 fieldset.collapsed {
-    border: 1px solid #eee;
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     overflow: hidden;
 }
 
 fieldset.collapsed h2 {
-    background: #f8f8f8;
-    color: #666;
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
 }
 
 fieldset .collapse-toggle {
-    color: #fff;
+    color: var(--body-bg);
 }
 
 fieldset.collapsed .collapse-toggle {
     background: transparent;
     display: inline;
-    color: #447e9b;
+    color: var(--link-fg);
 }
 
 /* MONOSPACE TEXTAREAS */
@@ -246,8 +246,8 @@ fieldset.monospace textarea {
 .submit-row {
     padding: 12px 14px;
     margin: 0 0 20px;
-    background: #f8f8f8;
-    border: 1px solid #eee;
+    background: var(--darkened-bg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     text-align: right;
     overflow: hidden;
@@ -279,35 +279,35 @@ body.popup .submit-row {
 
 .submit-row a.deletelink {
     display: block;
-    background: #ba2121;
+    background: var(--delete-button-bg);
     border-radius: 4px;
     padding: 10px 15px;
     height: 15px;
     line-height: 15px;
-    color: #fff;
+    color: var(--button-fg);
 }
 
 .submit-row a.closelink {
     display: inline-block;
-    background: #bbbbbb;
+    background: var(--close-button-bg);
     border-radius: 4px;
     padding: 10px 15px;
     height: 15px;
     line-height: 15px;
     margin: 0 0 0 5px;
-    color: #fff;
+    color: var(--button-fg);
 }
 
 .submit-row a.deletelink:focus,
 .submit-row a.deletelink:hover,
 .submit-row a.deletelink:active {
-    background: #a41515;
+    background: var(--delete-button-hover-bg);
 }
 
 .submit-row a.closelink:focus,
 .submit-row a.closelink:hover,
 .submit-row a.closelink:active {
-    background: #aaaaaa;
+    background: var(--close-button-hover-bg);
 }
 
 /* CUSTOM FORM FIELDS */
@@ -386,12 +386,12 @@ body.popup .submit-row {
 
 .inline-related h3 {
     margin: 0;
-    color: #666;
+    color: var(--body-quiet-color);
     padding: 5px;
     font-size: 13px;
-    background: #f8f8f8;
-    border-top: 1px solid #eee;
-    border-bottom: 1px solid #eee;
+    background: var(--darkened-bg);
+    border-top: 1px solid var(--hairline-color);
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 .inline-related h3 span.delete {
@@ -405,7 +405,7 @@ body.popup .submit-row {
 
 .inline-related fieldset {
     margin: 0;
-    background: #fff;
+    background: var(--body-bg);
     border: none;
     width: 100%;
 }
@@ -417,7 +417,7 @@ body.popup .submit-row {
     text-align: left;
     font-weight: bold;
     background: #bcd;
-    color: #fff;
+    color: var(--body-bg);
 }
 
 .inline-group .tabular fieldset.module {
@@ -456,7 +456,7 @@ body.popup .submit-row {
     overflow: hidden;
     font-size: 9px;
     font-weight: bold;
-    color: #666;
+    color: var(--body-quiet-color);
     _width: 700px;
 }
 
@@ -473,15 +473,15 @@ body.popup .submit-row {
 
 .inline-group div.add-row,
 .inline-group .tabular tr.add-row td {
-    color: #666;
-    background: #f8f8f8;
+    color: var(--body-quiet-color);
+    background: var(--darkened-bg);
     padding: 8px 10px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 .inline-group .tabular tr.add-row td {
     padding: 8px 10px;
-    border-bottom: 1px solid #eee;
+    border-bottom: 1px solid var(--hairline-color);
 }
 
 .inline-group ul.tools a.add,

+ 4 - 4
django/contrib/admin/static/admin/css/login.css

@@ -1,7 +1,7 @@
 /* LOGIN FORM */
 
 .login {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
     height: auto;
 }
 
@@ -16,7 +16,7 @@
 }
 
 .login #header h1 a {
-    color: #fff;
+    color: var(--body-bg);
 }
 
 .login #content {
@@ -24,8 +24,8 @@
 }
 
 .login #container {
-    background: #fff;
-    border: 1px solid #eaeaea;
+    background: var(--body-bg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     overflow: hidden;
     width: 28em;

+ 10 - 10
django/contrib/admin/static/admin/css/nav_sidebar.css

@@ -12,22 +12,22 @@
     justify-content: center;
     flex: 0 0 23px;
     width: 23px;
-    border-right: 1px solid #eaeaea;
-    background-color: #ffffff;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
     cursor: pointer;
     font-size: 20px;
-    color: #447e9b;
+    color: var(--link-fg);
     padding: 0;
 }
 
 [dir="rtl"] .toggle-nav-sidebar {
-    border-left: 1px solid #eaeaea;
+    border-left: 1px solid var(--hairline-color);
     border-right: 0;
 }
 
 .toggle-nav-sidebar:hover,
 .toggle-nav-sidebar:focus {
-    background-color: #f6f6f6;
+    background-color: var(--darkened-bg);
 }
 
 #nav-sidebar {
@@ -36,13 +36,13 @@
     left: -276px;
     margin-left: -276px;
     border-top: 1px solid transparent;
-    border-right: 1px solid #eaeaea;
-    background-color: #ffffff;
+    border-right: 1px solid var(--hairline-color);
+    background-color: var(--body-bg);
     overflow: auto;
 }
 
 [dir="rtl"] #nav-sidebar {
-    border-left: 1px solid #eaeaea;
+    border-left: 1px solid var(--hairline-color);
     border-right: 0;
     left: 0;
     margin-left: 0;
@@ -91,12 +91,12 @@
 
 #nav-sidebar .current-app .section:link,
 #nav-sidebar .current-app .section:visited {
-    color: #ffc;
+    color: var(--selected-row);
     font-weight: bold;
 }
 
 #nav-sidebar .current-model {
-    background: #ffc;
+    background: var(--selected-row);
 }
 
 .main > #nav-sidebar + .content {

+ 14 - 13
django/contrib/admin/static/admin/css/responsive.css

@@ -140,7 +140,7 @@ input[type="submit"], button {
     }
 
     #changelist .actions select {
-        background: #fff;
+        background: var(--body-bg);
     }
 
     #changelist .actions .button {
@@ -166,7 +166,7 @@ input[type="submit"], button {
     .filtered .actions,
 
     #changelist .paginator {
-        border-top-color: #eee;
+        border-top-color: var(--hairline-color); /* XXX Is this used at all? */
     }
 
     #changelist .results + .paginator {
@@ -213,7 +213,7 @@ input[type="submit"], button {
     fieldset .fieldBox + .fieldBox {
         margin-top: 10px;
         padding-top: 10px;
-        border-top: 1px solid #eee;
+        border-top: 1px solid var(--hairline-color);
     }
 
     textarea {
@@ -399,11 +399,11 @@ input[type="submit"], button {
     .datetime .timezonewarning {
         display: block;
         font-size: 11px;
-        color: #999;
+        color: var(--body-quiet-color);
     }
 
     .datetimeshortcuts {
-        color: #ccc;
+        color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
     }
 
     .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@@ -740,7 +740,7 @@ input[type="submit"], button {
     /* Inlines */
 
     .inline-group[data-inline-type="stacked"] .inline-related {
-        border: 2px solid #eee;
+        border: 1px solid var(--hairline-color);
         border-radius: 4px;
         margin-top: 15px;
         overflow: auto;
@@ -750,18 +750,19 @@ input[type="submit"], button {
         box-sizing: border-box;
     }
 
-    .inline-group[data-inline-type="stacked"] .inline-related + .inline-related {
-        margin-top: 30px;
-    }
-
     .inline-group[data-inline-type="stacked"] .inline-related .module {
         padding: 0 10px;
     }
 
-    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:last-child {
+    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
+        border-top: 1px solid var(--hairline-color);
         border-bottom: none;
     }
 
+    .inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
+        border-top: none;
+    }
+
     .inline-group[data-inline-type="stacked"] .inline-related h3 {
         padding: 10px;
         border-top-width: 0;
@@ -791,7 +792,7 @@ input[type="submit"], button {
 
     .inline-group[data-inline-type="stacked"] div.add-row {
         margin-top: 15px;
-        border: 1px solid #eee;
+        border: 1px solid var(--hairline-color);
         border-radius: 4px;
     }
 
@@ -961,7 +962,7 @@ input[type="submit"], button {
     }
 
     .timelist a {
-        background: #fff;
+        background: var(--body-bg);
         padding: 4px;
     }
 

+ 1 - 1
django/contrib/admin/static/admin/css/rtl.css

@@ -97,7 +97,7 @@ thead th.sorted .text {
     border-left: none;
     padding-left: 10px;
     margin-left: 0;
-    border-right: 5px solid #eaeaea;
+    border-right: 5px solid var(--hairline-color);
     padding-right: 10px;
     margin-right: -15px;
 }

+ 36 - 36
django/contrib/admin/static/admin/css/widgets.css

@@ -22,26 +22,25 @@
 }
 
 .selector-available h2, .selector-chosen h2 {
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-radius: 4px 4px 0 0;
 }
 
 .selector-chosen h2 {
-    background: #79aec8;
-    color: #fff;
+    background: var(--primary);
+    color: var(--body-bg);
 }
 
 .selector .selector-available h2 {
-    background: #f8f8f8;
-    color: #666;
+    background: var(--darkened-bg);
+    color: var(--body-quiet-color);
 }
 
 .selector .selector-filter {
-    background: white;
-    border: 1px solid #ccc;
+    border: 1px solid var(--border-color);
     border-width: 0 1px;
     padding: 8px;
-    color: #999;
+    color: var(--body-quiet-color);
     font-size: 10px;
     margin: 0;
     text-align: left;
@@ -66,7 +65,7 @@
 .selector ul.selector-chooser {
     float: left;
     width: 22px;
-    background-color: #eee;
+    background-color: var(--selected-bg);
     border-radius: 10px;
     margin: 10em 5px 0 5px;
     padding: 0;
@@ -126,14 +125,14 @@ a.selector-chooseall, a.selector-clearall {
     overflow: hidden;
     font-weight: bold;
     line-height: 16px;
-    color: #666;
+    color: var(--body-quiet-color);
     text-decoration: none;
     opacity: 0.3;
 }
 
 a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
 a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
-    color: #447e9b;
+    color: var(--link-fg);
 }
 
 a.active.selector-chooseall, a.active.selector-clearall {
@@ -261,7 +260,7 @@ p.datetime {
     line-height: 20px;
     margin: 0;
     padding: 0;
-    color: #666;
+    color: var(--body-quiet-color);
     font-weight: bold;
 }
 
@@ -269,7 +268,7 @@ p.datetime {
     white-space: nowrap;
     font-weight: normal;
     font-size: 11px;
-    color: #ccc;
+    color: var(--border-color);
 }
 
 .datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
@@ -313,7 +312,7 @@ table p.datetime {
 
 .timezonewarning {
     font-size: 11px;
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 /* URL */
@@ -322,7 +321,7 @@ p.url {
     line-height: 20px;
     margin: 0;
     padding: 0;
-    color: #666;
+    color: var(--body-quiet-color);
     font-size: 11px;
     font-weight: bold;
 }
@@ -337,7 +336,7 @@ p.file-upload {
     line-height: 20px;
     margin: 0;
     padding: 0;
-    color: #666;
+    color: var(--body-quiet-color);
     font-size: 11px;
     font-weight: bold;
 }
@@ -355,7 +354,7 @@ p.file-upload {
 }
 
 span.clearable-file-input label {
-    color: #333;
+    color: var(--body-fg);
     font-size: 11px;
     display: inline;
     float: none;
@@ -368,8 +367,9 @@ span.clearable-file-input label {
     font-size: 12px;
     width: 19em;
     text-align: center;
-    background: white;
-    border: 1px solid #ddd;
+    background: var(--body-bg);
+    color: var(--body-fg);
+    border: 1px solid var(--hairline-color);
     border-radius: 4px;
     box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
     overflow: hidden;
@@ -397,20 +397,20 @@ span.clearable-file-input label {
     margin: 0;
     text-align: center;
     border-top: none;
-    background: #f5dd5d;
     font-weight: 700;
     font-size: 12px;
     color: #333;
+    background: var(--accent);
 }
 
 .calendar th {
     padding: 8px 5px;
-    background: #f8f8f8;
-    border-bottom: 1px solid #ddd;
+    background: var(--darkened-bg);
+    border-bottom: 1px solid var(--border-color);
     font-weight: 400;
     font-size: 12px;
     text-align: center;
-    color: #666;
+    color: var(--body-quiet-color);
 }
 
 .calendar td {
@@ -418,17 +418,17 @@ span.clearable-file-input label {
     font-size: 12px;
     text-align: center;
     padding: 0;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
     border-bottom: none;
 }
 
 .calendar td.selected a {
-    background: #79aec8;
-    color: #fff;
+    background: var(--primary);
+    color: var(--button-fg);
 }
 
 .calendar td.nonday {
-    background: #f8f8f8;
+    background: var(--darkened-bg);
 }
 
 .calendar td.today a {
@@ -440,17 +440,17 @@ span.clearable-file-input label {
     font-weight: 400;
     padding: 6px;
     text-decoration: none;
-    color: #444;
+    color: var(--body-quiet-color);
 }
 
 .calendar td a:focus, .timelist a:focus,
 .calendar td a:hover, .timelist a:hover {
-    background: #79aec8;
+    background: var(--primary);
     color: white;
 }
 
 .calendar td a:active, .timelist a:active {
-    background: #417690;
+    background: var(--header-bg);
     color: white;
 }
 
@@ -464,16 +464,16 @@ span.clearable-file-input label {
 
 .calendarnav a:link, #calendarnav a:visited,
 #calendarnav a:focus, #calendarnav a:hover {
-    color: #999;
+    color: var(--body-quiet-color);
 }
 
 .calendar-shortcuts {
-    background: white;
+    background: var(--body-bg);
+    color: var(--body-quiet-color);
     font-size: 11px;
     line-height: 11px;
-    border-top: 1px solid #eee;
+    border-top: 1px solid var(--hairline-color);
     padding: 8px 0;
-    color: #ccc;
 }
 
 .calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
@@ -511,8 +511,8 @@ span.clearable-file-input label {
     padding: 4px 0;
     font-size: 12px;
     background: #eee;
-    border-top: 1px solid #ddd;
-    color: #333;
+    border-top: 1px solid var(--border-color);
+    color: var(--body-fg);
 }
 
 .calendar-cancel:focus, .calendar-cancel:hover {

+ 28 - 0
docs/ref/contrib/admin/index.txt

@@ -2802,6 +2802,34 @@ creating your own ``AdminSite`` instance (see below), and changing the
 :attr:`AdminSite.index_template` , :attr:`AdminSite.login_template` or
 :attr:`AdminSite.logout_template` properties.
 
+.. _admin-theming:
+
+Theming support
+===============
+
+The admin uses CSS variables to define colors. This allows changing colors
+without having to override many individual CSS rules. For example, if you
+preferred purple instead of blue you could add a ``admin/base.html`` template
+override to your project:
+
+.. code-block:: html+django
+
+    {% extends 'admin/base.html' %}
+
+    {% block extrahead %}{{ block.super }}
+    <style>
+    :root {
+      --primary: #9774d5;
+      --secondary: #785cab;
+      --link-fg: #7c449b;
+      --link-selected-fg: #8f5bb2;
+    }
+    </style>
+    {% endblock %}
+
+An up-to-date list of CSS variables is at
+:file:`django/contrib/admin/static/admin/css/base.css`.
+
 ``AdminSite`` objects
 =====================
 

+ 2 - 0
docs/releases/3.2.txt

@@ -117,6 +117,8 @@ Minor features
 * Read-only related fields are now rendered as navigable links if target models
   are registered in the admin.
 
+* The admin now supports theming. See :ref:`admin-theming` for more details.
+
 :mod:`django.contrib.admindocs`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 1 - 0
docs/spelling_wordlist

@@ -676,6 +676,7 @@ textarea
 th
 that'll
 Thejaswi
+theming
 This'll
 threadlocals
 threadpool