[{"data":1,"prerenderedAt":2370},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral":2337,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral":2345,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral":2350,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral":2351},{"id":4,"title":5,"body":6,"dateModified":2304,"datePublished":2304,"description":2305,"extension":2306,"faq":2307,"keyword":2316,"kind":2317,"meta":2318,"navigation":161,"path":2326,"seo":2327,"slug":2330,"stem":2331,"topics":2332,"type":2335,"__hash__":2336},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral\u002Findex.md","Apply Built-In Cell Styles Like Good, Bad and Neutral",{"type":7,"value":8,"toc":2292},"minimark",[9,72,77,106,122,126,271,354,500,504,507,1125,1151,1359,1363,1366,1630,1642,1646,1649,1974,1994,2139,2143,2155,2159,2201,2205,2229,2233,2239,2245,2251,2257,2261,2288],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,29,37,29,40,29,43,29,46,29,49,52,53,55,56,58,59,61,62,65,66,71],"p",{},"Excel ships with a gallery of named cell styles — ",[14,15,16],"em",{},"Good"," in green, ",[14,19,20],{},"Bad"," in red, ",[14,23,24],{},"Neutral"," in yellow, ",[14,27,28],{},"Title",", ",[14,31,32],{},"Heading 1"," to ",[14,35,36],{},"4",[14,38,39],{},"Input",[14,41,42],{},"Calculation",[14,44,45],{},"Check Cell",[14,47,48],{},"Total",[14,50,51],{},"Note"," and a family of accent colours. People who build models by hand use them because they carry meaning: an ",[14,54,39],{}," cell is one you may change, a ",[14,57,42],{}," cell is a formula, ",[14,60,20],{}," flags a problem. A generated report that uses the same styles fits in with the rest of a finance team's workbooks, and anyone who edits it later finds the styles in Excel's ",[14,63,64],{},"Cell Styles"," gallery rather than a scatter of one-off colours. openpyxl knows all of them and can apply them by name. The catch is that some of its names differ from what Excel's gallery shows. This page lists the names, applies status styles to a KPI table by value, combines styles with number formats and direct formatting, checks the colours in a rendered copy, and compares the approach with conditional formatting. It belongs to ",[67,68,70],"a",{"href":69},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",".",[73,74,76],"h2",{"id":75},"prerequisites","Prerequisites",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pillow\n","bash","",[85,86,87],"code",{"__ignoreMap":83},[88,89,92,96,100,103],"span",{"class":90,"line":91},"line",1,[88,93,95],{"class":94},"sMTad","pip",[88,97,99],{"class":98},"srMev"," install",[88,101,102],{"class":98}," openpyxl",[88,104,105],{"class":98}," pillow\n",[10,107,108,109,112,113,116,117,121],{},"LibreOffice (",[85,110,111],{},"soffice",") and ",[85,114,115],{},"pdftoppm"," are used only to render the workbook and check the colours. Custom named styles of your own — a company theme — are covered in ",[67,118,120],{"href":119},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report\u002F","Apply a Reusable Style Theme Across an Excel Report","; this page is about the ones Excel already has.",[73,123,125],{"id":124},"step-1-the-names-openpyxl-uses","Step 1 — The names openpyxl uses",[78,127,131],{"className":128,"code":129,"language":130,"meta":83,"style":83},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl.styles.builtins import styles as BUILTIN\n\nprint(len(BUILTIN), \"built-in styles\")\nprint([n for n in BUILTIN if not n.startswith((\"20 %\", \"40 %\", \"60 %\"))])\nprint(BUILTIN[\"Good\"].fill.fgColor.rgb, BUILTIN[\"Bad\"].fill.fgColor.rgb, BUILTIN[\"Neutral\"].fill.fgColor.rgb)\n","python",[85,132,133,156,163,189,234],{"__ignoreMap":83},[88,134,135,139,143,146,149,152],{"class":90,"line":91},[88,136,138],{"class":137},"s-kum","from",[88,140,142],{"class":141},"skGVy"," openpyxl.styles.builtins ",[88,144,145],{"class":137},"import",[88,147,148],{"class":141}," styles ",[88,150,151],{"class":137},"as",[88,153,155],{"class":154},"sP0c6"," BUILTIN\n",[88,157,159],{"class":90,"line":158},2,[88,160,162],{"emptyLinePlaceholder":161},true,"\n",[88,164,166,169,172,175,177,180,183,186],{"class":90,"line":165},3,[88,167,168],{"class":154},"print",[88,170,171],{"class":141},"(",[88,173,174],{"class":154},"len",[88,176,171],{"class":141},[88,178,179],{"class":154},"BUILTIN",[88,181,182],{"class":141},"), ",[88,184,185],{"class":98},"\"built-in styles\"",[88,187,188],{"class":141},")\n",[88,190,192,194,197,200,203,206,209,212,215,218,221,223,226,228,231],{"class":90,"line":191},4,[88,193,168],{"class":154},[88,195,196],{"class":141},"([n ",[88,198,199],{"class":137},"for",[88,201,202],{"class":141}," n ",[88,204,205],{"class":137},"in",[88,207,208],{"class":154}," BUILTIN",[88,210,211],{"class":137}," if",[88,213,214],{"class":137}," not",[88,216,217],{"class":141}," n.startswith((",[88,219,220],{"class":98},"\"20 %\"",[88,222,29],{"class":141},[88,224,225],{"class":98},"\"40 %\"",[88,227,29],{"class":141},[88,229,230],{"class":98},"\"60 %\"",[88,232,233],{"class":141},"))])\n",[88,235,237,239,241,243,246,249,252,254,256,259,261,263,265,268],{"class":90,"line":236},5,[88,238,168],{"class":154},[88,240,171],{"class":141},[88,242,179],{"class":154},[88,244,245],{"class":141},"[",[88,247,248],{"class":98},"\"Good\"",[88,250,251],{"class":141},"].fill.fgColor.rgb, ",[88,253,179],{"class":154},[88,255,245],{"class":141},[88,257,258],{"class":98},"\"Bad\"",[88,260,251],{"class":141},[88,262,179],{"class":154},[88,264,245],{"class":141},[88,266,267],{"class":98},"\"Neutral\"",[88,269,270],{"class":141},"].fill.fgColor.rgb)\n",[10,272,273,274,277,278,281,282,29,284,29,286,29,288,29,290,29,292,29,295,29,297,29,299,29,302,29,304,29,306,29,309,29,312,29,315,29,318,321,322,33,324,327,328,33,331,334,335,338,339,342,343,346,347,350,351,353],{},"openpyxl defines Excel's built-in styles in ",[85,275,276],{},"openpyxl.styles.builtins",", each with the fonts, fills, borders and number formats Excel uses, and the ",[85,279,280],{},"builtinId"," that identifies it to Excel. Most names match Excel's gallery exactly — ",[14,283,16],{},[14,285,20],{},[14,287,24],{},[14,289,28],{},[14,291,39],{},[14,293,294],{},"Output",[14,296,42],{},[14,298,45],{},[14,300,301],{},"Linked Cell",[14,303,48],{},[14,305,51],{},[14,307,308],{},"Warning Text",[14,310,311],{},"Explanatory Text",[14,313,314],{},"Comma",[14,316,317],{},"Currency",[14,319,320],{},"Percent",". Two families do not: Excel's ",[14,323,32],{},[14,325,326],{},"Heading 4"," are ",[85,329,330],{},"Headline 1",[85,332,333],{},"Headline 4"," in openpyxl, and ",[14,336,337],{},"20% - Accent1"," is ",[85,340,341],{},"20 % - Accent1",", with a space before the percent sign. Using Excel's spelling raises ",[85,344,345],{},"ValueError: Heading 1 is not a known style",", so copy names from this list. The last entry, ",[85,348,349],{},"Pandas",", is not an Excel style at all — openpyxl defines it for the header cells pandas writes — and is best ignored. In the file the style is identified by its ",[85,352,280],{},", so Excel shows it under its own name — in the user's language — whatever openpyxl calls it.",[355,356,364,365,364,369,364,373,364,380,364,388,364,395,364,402,364,405,364,409,364,414,364,420,364,422,364,427,364,429,364,432,364,435,364,439,364,441,364,445,364,447,364,449,364,452,364,456,364,458,364,462,364,464,364,467,364,470,364,474,364,476,364,479,364,481,364,483,364,486,364,490,364,492,364,496,364,498],"svg",{"viewBox":357,"role":358,"ariaLabelledBy":359,"xmlns":362,"style":363},"4 0 752 242","img",[360,361],"bs-names-t","bs-names-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[366,367,368],"title",{"id":360},"Excel's gallery names and openpyxl's names",[370,371,372],"desc",{"id":361},"Good, Bad, Neutral, Title, Input, Calculation, Total and Note have the same names in Excel and openpyxl. Excel's Heading 1 to 4 are Headline 1 to 4 in openpyxl, and 20% - Accent1 is written with a space before the percent sign.",[374,375],"rect",{"x":36,"y":376,"width":377,"height":378,"fill":379},"0","752","242","#ffffff",[374,381],{"x":382,"y":383,"width":384,"height":385,"rx":376,"fill":379,"stroke":386,"style":387},"20.0","16.0","100.8","40.0","var(--line,#cdd5e6)","stroke-width:1px",[374,389],{"x":390,"y":383,"width":391,"height":385,"rx":376,"fill":392,"stroke":393,"style":394},"120.8","309.6","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[396,397,401],"text",{"x":398,"y":399,"style":400},"275.6","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel gallery",[374,403],{"x":404,"y":383,"width":391,"height":385,"rx":376,"fill":392,"stroke":393,"style":394},"430.4",[396,406,408],{"x":407,"y":399,"style":400},"585.2","openpyxl name",[374,410],{"x":382,"y":411,"width":384,"height":412,"rx":376,"fill":413,"stroke":386,"style":387},"56.0","34.0","#e7ebef",[396,415,419],{"x":416,"y":417,"style":418},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","status",[374,421],{"x":390,"y":411,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,423,426],{"x":398,"y":424,"style":425},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Good, Bad, Neutral",[374,428],{"x":404,"y":411,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,430,431],{"x":407,"y":424,"style":425},"same",[374,433],{"x":382,"y":434,"width":384,"height":412,"rx":376,"fill":413,"stroke":386,"style":387},"90.0",[396,436,438],{"x":416,"y":437,"style":418},"112.0","model cells",[374,440],{"x":390,"y":434,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,442,444],{"x":398,"y":443,"style":425},"111.0","Input, Calculation, Output",[374,446],{"x":404,"y":434,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,448,431],{"x":407,"y":443,"style":425},[374,450],{"x":382,"y":451,"width":384,"height":412,"rx":376,"fill":413,"stroke":386,"style":387},"124.0",[396,453,455],{"x":416,"y":454,"style":418},"146.0","headings",[374,457],{"x":390,"y":451,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,459,461],{"x":398,"y":460,"style":425},"145.0","Heading 1 – 4",[374,463],{"x":404,"y":451,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,465,466],{"x":407,"y":460,"style":425},"Headline 1 – 4",[374,468],{"x":382,"y":469,"width":384,"height":412,"rx":376,"fill":413,"stroke":386,"style":387},"158.0",[396,471,473],{"x":416,"y":472,"style":418},"180.0","accents",[374,475],{"x":390,"y":469,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,477,337],{"x":398,"y":478,"style":425},"179.0",[374,480],{"x":404,"y":469,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,482,341],{"x":407,"y":478,"style":425},[374,484],{"x":382,"y":485,"width":384,"height":412,"rx":376,"fill":413,"stroke":386,"style":387},"192.0",[396,487,489],{"x":416,"y":488,"style":418},"214.0","numbers",[374,491],{"x":390,"y":485,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,493,495],{"x":398,"y":494,"style":425},"213.0","Comma, Currency, Percent",[374,497],{"x":404,"y":485,"width":391,"height":412,"rx":376,"fill":379,"stroke":386,"style":387},[396,499,431],{"x":407,"y":494,"style":425},[73,501,503],{"id":502},"step-2-apply-status-styles-by-value","Step 2 — Apply status styles by value",[10,505,506],{},"A KPI table where each result is marked Good, Neutral or Bad against its target:",[78,508,510],{"className":128,"code":509,"language":130,"meta":83,"style":83},"from openpyxl import Workbook\n\nkpis = [(\"Revenue (k)\", 1284, 1200, \"up\"), (\"Gross margin %\", 31.2, 33.0, \"up\"), (\"Orders\", 3412, 3400, \"up\"),\n        (\"Return rate %\", 2.1, 2.5, \"down\"), (\"Days sales outstanding\", 52, 45, \"down\")]\n\nwb = Workbook()\nws = wb.active\nws.title = \"KPIs\"\nws[\"A1\"] = \"Monthly KPIs — September 2026\"\nws[\"A1\"].style = \"Title\"\nws.append([])\nws.append([\"Measure\", \"Actual\", \"Target\", \"Status\"])\nfor cell in ws[3]:\n    cell.style = \"Headline 3\"\n\ndef status(actual, target, good_when, tolerance=0.02):\n    ratio = actual \u002F target if good_when == \"up\" else target \u002F actual\n    return \"Good\" if ratio >= 1 else \"Neutral\" if ratio >= 1 - tolerance else \"Bad\"\n\nfor name, actual, target, good_when in kpis:\n    ws.append([name, actual, target, status(actual, target, good_when)])\n    r = ws.max_row\n    ws[f\"B{r}\"].style = \"Calculation\"\n    ws[f\"C{r}\"].style = \"Input\"\n    ws[f\"D{r}\"].style = ws[f\"D{r}\"].value\nws.column_dimensions[\"A\"].width = 26\nfor col in \"BCD\":\n    ws.column_dimensions[col].width = 12\nprint([(ws[f\"A{r}\"].value, ws[f\"D{r}\"].style) for r in range(4, ws.max_row + 1)])\n",[85,511,512,524,528,599,644,648,659,670,681,698,713,719,746,765,776,781,802,841,886,891,904,910,921,953,978,1017,1034,1050,1061],{"__ignoreMap":83},[88,513,514,516,519,521],{"class":90,"line":91},[88,515,138],{"class":137},[88,517,518],{"class":141}," openpyxl ",[88,520,145],{"class":137},[88,522,523],{"class":141}," Workbook\n",[88,525,526],{"class":90,"line":158},[88,527,162],{"emptyLinePlaceholder":161},[88,529,530,533,536,539,542,544,547,549,552,554,557,560,563,565,568,570,573,575,577,579,582,584,587,589,592,594,596],{"class":90,"line":165},[88,531,532],{"class":141},"kpis ",[88,534,535],{"class":137},"=",[88,537,538],{"class":141}," [(",[88,540,541],{"class":98},"\"Revenue (k)\"",[88,543,29],{"class":141},[88,545,546],{"class":154},"1284",[88,548,29],{"class":141},[88,550,551],{"class":154},"1200",[88,553,29],{"class":141},[88,555,556],{"class":98},"\"up\"",[88,558,559],{"class":141},"), (",[88,561,562],{"class":98},"\"Gross margin %\"",[88,564,29],{"class":141},[88,566,567],{"class":154},"31.2",[88,569,29],{"class":141},[88,571,572],{"class":154},"33.0",[88,574,29],{"class":141},[88,576,556],{"class":98},[88,578,559],{"class":141},[88,580,581],{"class":98},"\"Orders\"",[88,583,29],{"class":141},[88,585,586],{"class":154},"3412",[88,588,29],{"class":141},[88,590,591],{"class":154},"3400",[88,593,29],{"class":141},[88,595,556],{"class":98},[88,597,598],{"class":141},"),\n",[88,600,601,604,607,609,612,614,617,619,622,624,627,629,632,634,637,639,641],{"class":90,"line":191},[88,602,603],{"class":141},"        (",[88,605,606],{"class":98},"\"Return rate %\"",[88,608,29],{"class":141},[88,610,611],{"class":154},"2.1",[88,613,29],{"class":141},[88,615,616],{"class":154},"2.5",[88,618,29],{"class":141},[88,620,621],{"class":98},"\"down\"",[88,623,559],{"class":141},[88,625,626],{"class":98},"\"Days sales outstanding\"",[88,628,29],{"class":141},[88,630,631],{"class":154},"52",[88,633,29],{"class":141},[88,635,636],{"class":154},"45",[88,638,29],{"class":141},[88,640,621],{"class":98},[88,642,643],{"class":141},")]\n",[88,645,646],{"class":90,"line":236},[88,647,162],{"emptyLinePlaceholder":161},[88,649,651,654,656],{"class":90,"line":650},6,[88,652,653],{"class":141},"wb ",[88,655,535],{"class":137},[88,657,658],{"class":141}," Workbook()\n",[88,660,662,665,667],{"class":90,"line":661},7,[88,663,664],{"class":141},"ws ",[88,666,535],{"class":137},[88,668,669],{"class":141}," wb.active\n",[88,671,673,676,678],{"class":90,"line":672},8,[88,674,675],{"class":141},"ws.title ",[88,677,535],{"class":137},[88,679,680],{"class":98}," \"KPIs\"\n",[88,682,684,687,690,693,695],{"class":90,"line":683},9,[88,685,686],{"class":141},"ws[",[88,688,689],{"class":98},"\"A1\"",[88,691,692],{"class":141},"] ",[88,694,535],{"class":137},[88,696,697],{"class":98}," \"Monthly KPIs — September 2026\"\n",[88,699,701,703,705,708,710],{"class":90,"line":700},10,[88,702,686],{"class":141},[88,704,689],{"class":98},[88,706,707],{"class":141},"].style ",[88,709,535],{"class":137},[88,711,712],{"class":98}," \"Title\"\n",[88,714,716],{"class":90,"line":715},11,[88,717,718],{"class":141},"ws.append([])\n",[88,720,722,725,728,730,733,735,738,740,743],{"class":90,"line":721},12,[88,723,724],{"class":141},"ws.append([",[88,726,727],{"class":98},"\"Measure\"",[88,729,29],{"class":141},[88,731,732],{"class":98},"\"Actual\"",[88,734,29],{"class":141},[88,736,737],{"class":98},"\"Target\"",[88,739,29],{"class":141},[88,741,742],{"class":98},"\"Status\"",[88,744,745],{"class":141},"])\n",[88,747,749,751,754,756,759,762],{"class":90,"line":748},13,[88,750,199],{"class":137},[88,752,753],{"class":141}," cell ",[88,755,205],{"class":137},[88,757,758],{"class":141}," ws[",[88,760,761],{"class":154},"3",[88,763,764],{"class":141},"]:\n",[88,766,768,771,773],{"class":90,"line":767},14,[88,769,770],{"class":141},"    cell.style ",[88,772,535],{"class":137},[88,774,775],{"class":98}," \"Headline 3\"\n",[88,777,779],{"class":90,"line":778},15,[88,780,162],{"emptyLinePlaceholder":161},[88,782,784,787,791,794,796,799],{"class":90,"line":783},16,[88,785,786],{"class":137},"def",[88,788,790],{"class":789},"s_Opv"," status",[88,792,793],{"class":141},"(actual, target, good_when, tolerance",[88,795,535],{"class":137},[88,797,798],{"class":154},"0.02",[88,800,801],{"class":141},"):\n",[88,803,805,808,810,813,816,819,822,825,828,831,834,836,838],{"class":90,"line":804},17,[88,806,807],{"class":141},"    ratio ",[88,809,535],{"class":137},[88,811,812],{"class":141}," actual ",[88,814,815],{"class":137},"\u002F",[88,817,818],{"class":141}," target ",[88,820,821],{"class":137},"if",[88,823,824],{"class":141}," good_when ",[88,826,827],{"class":137},"==",[88,829,830],{"class":98}," \"up\"",[88,832,833],{"class":137}," else",[88,835,818],{"class":141},[88,837,815],{"class":137},[88,839,840],{"class":141}," actual\n",[88,842,844,847,850,852,855,858,861,863,866,868,870,872,874,877,880,883],{"class":90,"line":843},18,[88,845,846],{"class":137},"    return",[88,848,849],{"class":98}," \"Good\"",[88,851,211],{"class":137},[88,853,854],{"class":141}," ratio ",[88,856,857],{"class":137},">=",[88,859,860],{"class":154}," 1",[88,862,833],{"class":137},[88,864,865],{"class":98}," \"Neutral\"",[88,867,211],{"class":137},[88,869,854],{"class":141},[88,871,857],{"class":137},[88,873,860],{"class":154},[88,875,876],{"class":137}," -",[88,878,879],{"class":141}," tolerance ",[88,881,882],{"class":137},"else",[88,884,885],{"class":98}," \"Bad\"\n",[88,887,889],{"class":90,"line":888},19,[88,890,162],{"emptyLinePlaceholder":161},[88,892,894,896,899,901],{"class":90,"line":893},20,[88,895,199],{"class":137},[88,897,898],{"class":141}," name, actual, target, good_when ",[88,900,205],{"class":137},[88,902,903],{"class":141}," kpis:\n",[88,905,907],{"class":90,"line":906},21,[88,908,909],{"class":141},"    ws.append([name, actual, target, status(actual, target, good_when)])\n",[88,911,913,916,918],{"class":90,"line":912},22,[88,914,915],{"class":141},"    r ",[88,917,535],{"class":137},[88,919,920],{"class":141}," ws.max_row\n",[88,922,924,927,930,933,937,940,943,946,948,950],{"class":90,"line":923},23,[88,925,926],{"class":141},"    ws[",[88,928,929],{"class":137},"f",[88,931,932],{"class":98},"\"B",[88,934,936],{"class":935},"sSjpA","{",[88,938,939],{"class":141},"r",[88,941,942],{"class":935},"}",[88,944,945],{"class":98},"\"",[88,947,707],{"class":141},[88,949,535],{"class":137},[88,951,952],{"class":98}," \"Calculation\"\n",[88,954,956,958,960,963,965,967,969,971,973,975],{"class":90,"line":955},24,[88,957,926],{"class":141},[88,959,929],{"class":137},[88,961,962],{"class":98},"\"C",[88,964,936],{"class":935},[88,966,939],{"class":141},[88,968,942],{"class":935},[88,970,945],{"class":98},[88,972,707],{"class":141},[88,974,535],{"class":137},[88,976,977],{"class":98}," \"Input\"\n",[88,979,981,983,985,988,990,992,994,996,998,1000,1002,1004,1006,1008,1010,1012,1014],{"class":90,"line":980},25,[88,982,926],{"class":141},[88,984,929],{"class":137},[88,986,987],{"class":98},"\"D",[88,989,936],{"class":935},[88,991,939],{"class":141},[88,993,942],{"class":935},[88,995,945],{"class":98},[88,997,707],{"class":141},[88,999,535],{"class":137},[88,1001,758],{"class":141},[88,1003,929],{"class":137},[88,1005,987],{"class":98},[88,1007,936],{"class":935},[88,1009,939],{"class":141},[88,1011,942],{"class":935},[88,1013,945],{"class":98},[88,1015,1016],{"class":141},"].value\n",[88,1018,1020,1023,1026,1029,1031],{"class":90,"line":1019},26,[88,1021,1022],{"class":141},"ws.column_dimensions[",[88,1024,1025],{"class":98},"\"A\"",[88,1027,1028],{"class":141},"].width ",[88,1030,535],{"class":137},[88,1032,1033],{"class":154}," 26\n",[88,1035,1037,1039,1042,1044,1047],{"class":90,"line":1036},27,[88,1038,199],{"class":137},[88,1040,1041],{"class":141}," col ",[88,1043,205],{"class":137},[88,1045,1046],{"class":98}," \"BCD\"",[88,1048,1049],{"class":141},":\n",[88,1051,1053,1056,1058],{"class":90,"line":1052},28,[88,1054,1055],{"class":141},"    ws.column_dimensions[col].width ",[88,1057,535],{"class":137},[88,1059,1060],{"class":154}," 12\n",[88,1062,1064,1066,1069,1071,1074,1076,1078,1080,1082,1085,1087,1089,1091,1093,1095,1097,1100,1102,1105,1107,1110,1112,1114,1117,1120,1122],{"class":90,"line":1063},29,[88,1065,168],{"class":154},[88,1067,1068],{"class":141},"([(ws[",[88,1070,929],{"class":137},[88,1072,1073],{"class":98},"\"A",[88,1075,936],{"class":935},[88,1077,939],{"class":141},[88,1079,942],{"class":935},[88,1081,945],{"class":98},[88,1083,1084],{"class":141},"].value, ws[",[88,1086,929],{"class":137},[88,1088,987],{"class":98},[88,1090,936],{"class":935},[88,1092,939],{"class":141},[88,1094,942],{"class":935},[88,1096,945],{"class":98},[88,1098,1099],{"class":141},"].style) ",[88,1101,199],{"class":137},[88,1103,1104],{"class":141}," r ",[88,1106,205],{"class":137},[88,1108,1109],{"class":154}," range",[88,1111,171],{"class":141},[88,1113,36],{"class":154},[88,1115,1116],{"class":141},", ws.max_row ",[88,1118,1119],{"class":137},"+",[88,1121,860],{"class":154},[88,1123,1124],{"class":141},")])\n",[10,1126,1127,1128,1131,1132,1134,1135,1137,1138,1140,1141,1143,1144,1147,1148,71],{},"Assigning a style name to ",[85,1129,1130],{},"cell.style"," registers the built-in style in the workbook the first time it is used and points the cell at it. The status rule allows a 2% tolerance, so being just short of target is ",[14,1133,24],{}," rather than ",[14,1136,20],{},"; for measures where lower is better, the ratio is inverted. Targets are styled ",[14,1139,39],{}," — orange fill, blue text — because they are the cells a reader might legitimately change, and actuals ",[14,1142,42],{},", the style Excel uses for computed values. The heading row uses ",[14,1145,1146],{},"Headline 3",", Excel's ",[14,1149,1150],{},"Heading 3",[355,1152,364,1158,364,1161,364,1164,364,1169,364,1173,364,1177,364,1182,364,1186,364,1190,364,1194,364,1198,364,1202,364,1206,364,1209,364,1214,364,1217,364,1221,364,1223,364,1226,364,1228,364,1231,364,1233,364,1236,364,1239,364,1243,364,1245,364,1249,364,1251,364,1253,364,1255,364,1257,364,1261,364,1264,364,1267,364,1270,364,1272,364,1275,364,1277,364,1279,364,1281,364,1283,364,1287,364,1290,364,1292,364,1295,364,1297,364,1300,364,1302,364,1304,364,1306,364,1308,364,1310,364,1312,364,1315,364,1319,364,1321,364,1324,364,1326,364,1328,364,1330,364,1332,364,1334,364,1336,364,1338,364,1342,364,1344,364,1347,364,1349,364,1351,364,1353,364,1355,364,1357],{"viewBox":1153,"role":358,"ariaLabelledBy":1154,"xmlns":362,"style":1157},"167 0 425 224",[1155,1156],"bs-kpis-t","bs-kpis-d","width:100%;max-width:425px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[366,1159,1160],{"id":1155},"The KPI table with built-in styles",[370,1162,1163],{"id":1156},"The title uses the Title style and the header row Heading 3. Actuals use Calculation and targets Input. The status column shows Revenue, Orders and Return rate as Good in green, Gross margin as Bad in red at 31.2 against 33.0, and Days sales outstanding as Bad at 52 against 45.",[374,1165],{"x":1166,"y":376,"width":1167,"height":1168,"fill":379},"167","425","224",[374,1170],{"x":1171,"y":383,"width":416,"height":1172,"rx":376,"fill":413,"stroke":386,"style":387},"183.9","24.0",[374,1174],{"x":1175,"y":383,"width":1176,"height":1172,"rx":376,"fill":413,"stroke":386,"style":387},"215.9","167.6",[396,1178,1181],{"x":1179,"y":572,"style":1180},"299.7","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[374,1183],{"x":1184,"y":383,"width":1185,"height":1172,"rx":376,"fill":413,"stroke":386,"style":387},"383.5","64.0",[396,1187,1189],{"x":1188,"y":572,"style":1180},"415.5","B",[374,1191],{"x":1192,"y":383,"width":1193,"height":1172,"rx":376,"fill":413,"stroke":386,"style":387},"447.5","64.9",[396,1195,1197],{"x":1196,"y":572,"style":1180},"479.9","C",[374,1199],{"x":1200,"y":383,"width":1201,"height":1172,"rx":376,"fill":413,"stroke":386,"style":387},"512.4","63.7",[396,1203,1205],{"x":1204,"y":572,"style":1180},"544.3","D",[374,1207],{"x":1171,"y":385,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},"28.0",[396,1210,1213],{"x":1211,"y":1212,"style":1180},"199.9","59.0","1",[374,1215],{"x":1175,"y":385,"width":1176,"height":1208,"rx":376,"fill":392,"stroke":393,"style":1216},"stroke-width:1.5px",[396,1218,1220],{"x":1179,"y":1212,"style":1219},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Measure",[374,1222],{"x":1184,"y":385,"width":1185,"height":1208,"rx":376,"fill":392,"stroke":393,"style":1216},[396,1224,1225],{"x":1188,"y":1212,"style":1219},"Actual",[374,1227],{"x":1192,"y":385,"width":1193,"height":1208,"rx":376,"fill":392,"stroke":393,"style":1216},[396,1229,1230],{"x":1196,"y":1212,"style":1219},"Target",[374,1232],{"x":1200,"y":385,"width":1201,"height":1208,"rx":376,"fill":392,"stroke":393,"style":1216},[396,1234,1235],{"x":1204,"y":1212,"style":1219},"Status",[374,1237],{"x":1171,"y":1238,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},"68.0",[396,1240,1242],{"x":1211,"y":1241,"style":1180},"87.0","2",[374,1244],{"x":1175,"y":1238,"width":1176,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1246,1248],{"x":1179,"y":1241,"style":1247},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Revenue (k)",[374,1250],{"x":1184,"y":1238,"width":1185,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1252,546],{"x":1188,"y":1241,"style":1247},[374,1254],{"x":1192,"y":1238,"width":1193,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1256,551],{"x":1196,"y":1241,"style":1247},[374,1258],{"x":1200,"y":1238,"width":1201,"height":1208,"rx":376,"fill":1259,"stroke":1260,"style":1216},"#d9f4f1","var(--teal,#0f9488)",[396,1262,16],{"x":1204,"y":1241,"style":1263},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[374,1265],{"x":1171,"y":1266,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},"96.0",[396,1268,761],{"x":1211,"y":1269,"style":1180},"115.0",[374,1271],{"x":1175,"y":1266,"width":1176,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1273,1274],{"x":1179,"y":1269,"style":1247},"Gross margin %",[374,1276],{"x":1184,"y":1266,"width":1185,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1278,567],{"x":1188,"y":1269,"style":1247},[374,1280],{"x":1192,"y":1266,"width":1193,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1282,572],{"x":1196,"y":1269,"style":1247},[374,1284],{"x":1200,"y":1266,"width":1201,"height":1208,"rx":376,"fill":1285,"stroke":1286,"style":1216},"#fce9e9","var(--danger,#dc2626)",[396,1288,20],{"x":1204,"y":1269,"style":1289},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[374,1291],{"x":1171,"y":451,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},[396,1293,36],{"x":1211,"y":1294,"style":1180},"143.0",[374,1296],{"x":1175,"y":451,"width":1176,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1298,1299],{"x":1179,"y":1294,"style":1247},"Orders",[374,1301],{"x":1184,"y":451,"width":1185,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1303,586],{"x":1188,"y":1294,"style":1247},[374,1305],{"x":1192,"y":451,"width":1193,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1307,591],{"x":1196,"y":1294,"style":1247},[374,1309],{"x":1200,"y":451,"width":1201,"height":1208,"rx":376,"fill":1259,"stroke":1260,"style":1216},[396,1311,16],{"x":1204,"y":1294,"style":1263},[374,1313],{"x":1171,"y":1314,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},"152.0",[396,1316,1318],{"x":1211,"y":1317,"style":1180},"171.0","5",[374,1320],{"x":1175,"y":1314,"width":1176,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1322,1323],{"x":1179,"y":1317,"style":1247},"Return rate %",[374,1325],{"x":1184,"y":1314,"width":1185,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1327,611],{"x":1188,"y":1317,"style":1247},[374,1329],{"x":1192,"y":1314,"width":1193,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1331,616],{"x":1196,"y":1317,"style":1247},[374,1333],{"x":1200,"y":1314,"width":1201,"height":1208,"rx":376,"fill":1259,"stroke":1260,"style":1216},[396,1335,16],{"x":1204,"y":1317,"style":1263},[374,1337],{"x":1171,"y":472,"width":416,"height":1208,"rx":376,"fill":413,"stroke":386,"style":387},[396,1339,1341],{"x":1211,"y":1340,"style":1180},"199.0","6",[374,1343],{"x":1175,"y":472,"width":1176,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1345,1346],{"x":1179,"y":1340,"style":1247},"Days sales outstanding",[374,1348],{"x":1184,"y":472,"width":1185,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1350,631],{"x":1188,"y":1340,"style":1247},[374,1352],{"x":1192,"y":472,"width":1193,"height":1208,"rx":376,"fill":379,"stroke":386,"style":387},[396,1354,636],{"x":1196,"y":1340,"style":1247},[374,1356],{"x":1200,"y":472,"width":1201,"height":1208,"rx":376,"fill":1285,"stroke":1286,"style":1216},[396,1358,20],{"x":1204,"y":1340,"style":1289},[73,1360,1362],{"id":1361},"step-3-number-formats-and-direct-formatting-on-top","Step 3 — Number formats and direct formatting on top",[10,1364,1365],{},"A named style sets a full set of formatting. Changing one attribute of a cell afterwards — its number format, say — keeps the style but adds a direct override:",[78,1367,1369],{"className":128,"code":1368,"language":130,"meta":83,"style":83},"from openpyxl.styles import Font\n\nfor r in range(4, ws.max_row + 1):\n    fmt = \"0.0\" if \"%\" in ws[f\"A{r}\"].value else \"#,##0\"\n    ws[f\"B{r}\"].number_format = fmt\n    ws[f\"C{r}\"].number_format = fmt\nws[\"A1\"].font = Font(name=\"Calibri\", size=16, bold=True, color=\"1F3864\")\nwb.save(\"kpis.xlsx\")\n\ncheck = __import__(\"openpyxl\").load_workbook(\"kpis.xlsx\")[\"KPIs\"]\nprint(\"B5:\", check[\"B5\"].style, check[\"B5\"].number_format, \"| A1:\", check[\"A1\"].style, check[\"A1\"].font.sz)\n",[85,1370,1371,1383,1387,1409,1449,1473,1495,1550,1560,1564,1593],{"__ignoreMap":83},[88,1372,1373,1375,1378,1380],{"class":90,"line":91},[88,1374,138],{"class":137},[88,1376,1377],{"class":141}," openpyxl.styles ",[88,1379,145],{"class":137},[88,1381,1382],{"class":141}," Font\n",[88,1384,1385],{"class":90,"line":158},[88,1386,162],{"emptyLinePlaceholder":161},[88,1388,1389,1391,1393,1395,1397,1399,1401,1403,1405,1407],{"class":90,"line":165},[88,1390,199],{"class":137},[88,1392,1104],{"class":141},[88,1394,205],{"class":137},[88,1396,1109],{"class":154},[88,1398,171],{"class":141},[88,1400,36],{"class":154},[88,1402,1116],{"class":141},[88,1404,1119],{"class":137},[88,1406,860],{"class":154},[88,1408,801],{"class":141},[88,1410,1411,1414,1416,1419,1421,1424,1427,1429,1431,1433,1435,1437,1439,1441,1444,1446],{"class":90,"line":191},[88,1412,1413],{"class":141},"    fmt ",[88,1415,535],{"class":137},[88,1417,1418],{"class":98}," \"0.0\"",[88,1420,211],{"class":137},[88,1422,1423],{"class":98}," \"%\"",[88,1425,1426],{"class":137}," in",[88,1428,758],{"class":141},[88,1430,929],{"class":137},[88,1432,1073],{"class":98},[88,1434,936],{"class":935},[88,1436,939],{"class":141},[88,1438,942],{"class":935},[88,1440,945],{"class":98},[88,1442,1443],{"class":141},"].value ",[88,1445,882],{"class":137},[88,1447,1448],{"class":98}," \"#,##0\"\n",[88,1450,1451,1453,1455,1457,1459,1461,1463,1465,1468,1470],{"class":90,"line":236},[88,1452,926],{"class":141},[88,1454,929],{"class":137},[88,1456,932],{"class":98},[88,1458,936],{"class":935},[88,1460,939],{"class":141},[88,1462,942],{"class":935},[88,1464,945],{"class":98},[88,1466,1467],{"class":141},"].number_format ",[88,1469,535],{"class":137},[88,1471,1472],{"class":141}," fmt\n",[88,1474,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493],{"class":90,"line":650},[88,1476,926],{"class":141},[88,1478,929],{"class":137},[88,1480,962],{"class":98},[88,1482,936],{"class":935},[88,1484,939],{"class":141},[88,1486,942],{"class":935},[88,1488,945],{"class":98},[88,1490,1467],{"class":141},[88,1492,535],{"class":137},[88,1494,1472],{"class":141},[88,1496,1497,1499,1501,1504,1506,1509,1513,1515,1518,1520,1523,1525,1528,1530,1533,1535,1538,1540,1543,1545,1548],{"class":90,"line":661},[88,1498,686],{"class":141},[88,1500,689],{"class":98},[88,1502,1503],{"class":141},"].font ",[88,1505,535],{"class":137},[88,1507,1508],{"class":141}," Font(",[88,1510,1512],{"class":1511},"sa561","name",[88,1514,535],{"class":137},[88,1516,1517],{"class":98},"\"Calibri\"",[88,1519,29],{"class":141},[88,1521,1522],{"class":1511},"size",[88,1524,535],{"class":137},[88,1526,1527],{"class":154},"16",[88,1529,29],{"class":141},[88,1531,1532],{"class":1511},"bold",[88,1534,535],{"class":137},[88,1536,1537],{"class":154},"True",[88,1539,29],{"class":141},[88,1541,1542],{"class":1511},"color",[88,1544,535],{"class":137},[88,1546,1547],{"class":98},"\"1F3864\"",[88,1549,188],{"class":141},[88,1551,1552,1555,1558],{"class":90,"line":672},[88,1553,1554],{"class":141},"wb.save(",[88,1556,1557],{"class":98},"\"kpis.xlsx\"",[88,1559,188],{"class":141},[88,1561,1562],{"class":90,"line":683},[88,1563,162],{"emptyLinePlaceholder":161},[88,1565,1566,1569,1571,1574,1576,1579,1582,1584,1587,1590],{"class":90,"line":700},[88,1567,1568],{"class":141},"check ",[88,1570,535],{"class":137},[88,1572,1573],{"class":154}," __import__",[88,1575,171],{"class":141},[88,1577,1578],{"class":98},"\"openpyxl\"",[88,1580,1581],{"class":141},").load_workbook(",[88,1583,1557],{"class":98},[88,1585,1586],{"class":141},")[",[88,1588,1589],{"class":98},"\"KPIs\"",[88,1591,1592],{"class":141},"]\n",[88,1594,1595,1597,1599,1602,1605,1608,1611,1613,1616,1619,1621,1623,1625,1627],{"class":90,"line":715},[88,1596,168],{"class":154},[88,1598,171],{"class":141},[88,1600,1601],{"class":98},"\"B5:\"",[88,1603,1604],{"class":141},", check[",[88,1606,1607],{"class":98},"\"B5\"",[88,1609,1610],{"class":141},"].style, check[",[88,1612,1607],{"class":98},[88,1614,1615],{"class":141},"].number_format, ",[88,1617,1618],{"class":98},"\"| A1:\"",[88,1620,1604],{"class":141},[88,1622,689],{"class":98},[88,1624,1610],{"class":141},[88,1626,689],{"class":98},[88,1628,1629],{"class":141},"].font.sz)\n",[10,1631,1632,1633,1635,1636,1638,1639,1641],{},"The actual for gross margin is still in the ",[14,1634,42],{}," style but now shows one decimal place, and the title is still ",[14,1637,28],{}," with a larger, darker font. In Excel both cells show their style as applied, with the overrides on top; if someone later modifies the ",[14,1640,42],{}," style in the gallery, cells using it update except for the attributes overridden directly. Keeping overrides to number formats, and leaving colours to the style, keeps the workbook consistent.",[73,1643,1645],{"id":1644},"step-4-check-the-colours-and-when-to-use-conditional-formatting","Step 4 — Check the colours, and when to use conditional formatting",[10,1647,1648],{},"Render the sheet and count the status colours:",[78,1650,1652],{"className":128,"code":1651,"language":130,"meta":83,"style":83},"import subprocess\nfrom PIL import Image\n\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"kpis.xlsx\"], check=True, capture_output=True)\nsubprocess.run([\"pdftoppm\", \"-png\", \"-r\", \"80\", \"-f\", \"1\", \"-l\", \"1\", \"kpis.pdf\", \"kpis\"], check=True)\ncounts = {c: n for n, c in Image.open(\"kpis-1.png\").convert(\"RGB\").getcolors(maxcolors=2_000_000)}\nfor name in [\"Good\", \"Neutral\", \"Bad\", \"Input\"]:\n    rgb = tuple(int(BUILTIN[name].fill.fgColor.rgb[i:i + 2], 16) for i in (2, 4, 6))\n    print(f\"{name:\u003C8} fill #{BUILTIN[name].fill.fgColor.rgb[2:]}  {counts.get(rgb, 0):>5} px\")\n",[85,1653,1654,1661,1674,1678,1726,1787,1830,1859,1916],{"__ignoreMap":83},[88,1655,1656,1658],{"class":90,"line":91},[88,1657,145],{"class":137},[88,1659,1660],{"class":141}," subprocess\n",[88,1662,1663,1665,1668,1671],{"class":90,"line":158},[88,1664,138],{"class":137},[88,1666,1667],{"class":154}," PIL",[88,1669,1670],{"class":137}," import",[88,1672,1673],{"class":141}," Image\n",[88,1675,1676],{"class":90,"line":165},[88,1677,162],{"emptyLinePlaceholder":161},[88,1679,1680,1683,1686,1688,1691,1693,1696,1698,1701,1703,1705,1708,1711,1713,1715,1717,1720,1722,1724],{"class":90,"line":191},[88,1681,1682],{"class":141},"subprocess.run([",[88,1684,1685],{"class":98},"\"soffice\"",[88,1687,29],{"class":141},[88,1689,1690],{"class":98},"\"--headless\"",[88,1692,29],{"class":141},[88,1694,1695],{"class":98},"\"--convert-to\"",[88,1697,29],{"class":141},[88,1699,1700],{"class":98},"\"pdf\"",[88,1702,29],{"class":141},[88,1704,1557],{"class":98},[88,1706,1707],{"class":141},"], ",[88,1709,1710],{"class":1511},"check",[88,1712,535],{"class":137},[88,1714,1537],{"class":154},[88,1716,29],{"class":141},[88,1718,1719],{"class":1511},"capture_output",[88,1721,535],{"class":137},[88,1723,1537],{"class":154},[88,1725,188],{"class":141},[88,1727,1728,1730,1733,1735,1738,1740,1743,1745,1748,1750,1753,1755,1758,1760,1763,1765,1767,1769,1772,1774,1777,1779,1781,1783,1785],{"class":90,"line":236},[88,1729,1682],{"class":141},[88,1731,1732],{"class":98},"\"pdftoppm\"",[88,1734,29],{"class":141},[88,1736,1737],{"class":98},"\"-png\"",[88,1739,29],{"class":141},[88,1741,1742],{"class":98},"\"-r\"",[88,1744,29],{"class":141},[88,1746,1747],{"class":98},"\"80\"",[88,1749,29],{"class":141},[88,1751,1752],{"class":98},"\"-f\"",[88,1754,29],{"class":141},[88,1756,1757],{"class":98},"\"1\"",[88,1759,29],{"class":141},[88,1761,1762],{"class":98},"\"-l\"",[88,1764,29],{"class":141},[88,1766,1757],{"class":98},[88,1768,29],{"class":141},[88,1770,1771],{"class":98},"\"kpis.pdf\"",[88,1773,29],{"class":141},[88,1775,1776],{"class":98},"\"kpis\"",[88,1778,1707],{"class":141},[88,1780,1710],{"class":1511},[88,1782,535],{"class":137},[88,1784,1537],{"class":154},[88,1786,188],{"class":141},[88,1788,1789,1792,1794,1797,1799,1802,1804,1807,1810,1813,1816,1819,1822,1824,1827],{"class":90,"line":650},[88,1790,1791],{"class":141},"counts ",[88,1793,535],{"class":137},[88,1795,1796],{"class":141}," {c: n ",[88,1798,199],{"class":137},[88,1800,1801],{"class":141}," n, c ",[88,1803,205],{"class":137},[88,1805,1806],{"class":141}," Image.open(",[88,1808,1809],{"class":98},"\"kpis-1.png\"",[88,1811,1812],{"class":141},").convert(",[88,1814,1815],{"class":98},"\"RGB\"",[88,1817,1818],{"class":141},").getcolors(",[88,1820,1821],{"class":1511},"maxcolors",[88,1823,535],{"class":137},[88,1825,1826],{"class":154},"2_000_000",[88,1828,1829],{"class":141},")}\n",[88,1831,1832,1834,1837,1839,1842,1844,1846,1848,1850,1852,1854,1857],{"class":90,"line":661},[88,1833,199],{"class":137},[88,1835,1836],{"class":141}," name ",[88,1838,205],{"class":137},[88,1840,1841],{"class":141}," [",[88,1843,248],{"class":98},[88,1845,29],{"class":141},[88,1847,267],{"class":98},[88,1849,29],{"class":141},[88,1851,258],{"class":98},[88,1853,29],{"class":141},[88,1855,1856],{"class":98},"\"Input\"",[88,1858,764],{"class":141},[88,1860,1861,1864,1866,1869,1871,1874,1876,1878,1881,1883,1886,1888,1890,1893,1895,1898,1900,1903,1905,1907,1909,1911,1913],{"class":90,"line":672},[88,1862,1863],{"class":141},"    rgb ",[88,1865,535],{"class":137},[88,1867,1868],{"class":154}," tuple",[88,1870,171],{"class":141},[88,1872,1873],{"class":154},"int",[88,1875,171],{"class":141},[88,1877,179],{"class":154},[88,1879,1880],{"class":141},"[name].fill.fgColor.rgb[i:i ",[88,1882,1119],{"class":137},[88,1884,1885],{"class":154}," 2",[88,1887,1707],{"class":141},[88,1889,1527],{"class":154},[88,1891,1892],{"class":141},") ",[88,1894,199],{"class":137},[88,1896,1897],{"class":141}," i ",[88,1899,205],{"class":137},[88,1901,1902],{"class":141}," (",[88,1904,1242],{"class":154},[88,1906,29],{"class":141},[88,1908,36],{"class":154},[88,1910,29],{"class":141},[88,1912,1341],{"class":154},[88,1914,1915],{"class":141},"))\n",[88,1917,1918,1921,1923,1925,1927,1929,1931,1934,1936,1939,1941,1943,1946,1948,1951,1953,1956,1959,1961,1964,1967,1969,1972],{"class":90,"line":683},[88,1919,1920],{"class":154},"    print",[88,1922,171],{"class":141},[88,1924,929],{"class":137},[88,1926,945],{"class":98},[88,1928,936],{"class":935},[88,1930,1512],{"class":141},[88,1932,1933],{"class":137},":\u003C8",[88,1935,942],{"class":935},[88,1937,1938],{"class":98}," fill #",[88,1940,936],{"class":935},[88,1942,179],{"class":154},[88,1944,1945],{"class":141},"[name].fill.fgColor.rgb[",[88,1947,1242],{"class":154},[88,1949,1950],{"class":141},":]",[88,1952,942],{"class":935},[88,1954,1955],{"class":935},"  {",[88,1957,1958],{"class":141},"counts.get(rgb, ",[88,1960,376],{"class":154},[88,1962,1963],{"class":141},")",[88,1965,1966],{"class":137},":>5",[88,1968,942],{"class":935},[88,1970,1971],{"class":98}," px\"",[88,1973,188],{"class":141},[10,1975,1976,1977,1979,1980,1982,1983,1985,1986,1988,1989,1993],{},"The green ",[14,1978,16],{}," fill and the red ",[14,1981,20],{}," fill appear, along with the orange ",[14,1984,39],{}," fill on the targets; ",[14,1987,24],{}," has no pixels because no KPI fell inside the 2% tolerance this month. These styles are applied once, from values known when the report is written, so they do not change if someone edits a number in Excel. When the colour must follow the value as it changes — an input sheet, a model people play with — use conditional formatting instead, which can apply the same colours through a rule, as in ",[67,1990,1992],{"href":1991},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl\u002F","Highlight Cells Above a Threshold with openpyxl",". For a finished report, built-in styles are simpler, faster to open and visible in the gallery.",[355,1995,364,2000,364,2003,364,2006,364,2009,364,2016,364,2027,364,2032,364,2036,364,2040,364,2043,364,2048,364,2055,364,2060,364,2065,364,2072,364,2077,364,2081,364,2083,364,2086,364,2090,364,2093,364,2098,364,2105,364,2109,364,2112,364,2114,364,2117,364,2121,364,2124,364,2127,364,2133,364,2136],{"viewBox":1996,"role":358,"ariaLabelledBy":1997,"xmlns":362,"style":363},"4 0 752 272",[1998,1999],"bs-when-t","bs-when-d",[366,2001,2002],{"id":1998},"Built-in style or conditional formatting?",[370,2004,2005],{"id":1999},"For a finished report whose values will not change, apply built-in styles from Python. For cells people will edit, where the colour should follow the value, use conditional formatting rules. For a house style of your own, define custom named styles.",[374,2007],{"x":36,"y":376,"width":377,"height":2008,"fill":379},"272",[374,2010],{"x":2011,"y":383,"width":2012,"height":2013,"rx":2014,"fill":392,"stroke":393,"style":2015},"183.7","392.7","75.5","12","stroke-width:2px",[2017,2018,2020],"g",{"transform":2019},"translate(368.0,29.0) scale(1.000)",[2021,2022],"path",{"d":2023,"fill":2024,"stroke":2025,"style":2026},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[396,2028,2031],{"x":2029,"y":2013,"style":2030},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Will the value change after the report is written?",[90,2033],{"x1":2029,"y1":2034,"x2":2029,"y2":2035,"stroke":393,"style":2015},"91.5","111.5",[90,2037],{"x1":2038,"y1":2035,"x2":2039,"y2":2035,"stroke":393,"style":2015},"134.7","625.3",[90,2041],{"x1":2038,"y1":2035,"x2":2038,"y2":2042,"stroke":393,"style":2015},"157.5",[2044,2045],"polygon",{"points":2046,"fill":2047},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[374,2049],{"x":2050,"y":2051,"width":2052,"height":2053,"rx":2054,"fill":379,"stroke":386,"style":394},"66.2","121.5","137.0","21.0","10",[396,2056,2059],{"x":2038,"y":2057,"style":2058},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","no, a finished report",[374,2061],{"x":382,"y":2062,"width":2063,"height":2064,"rx":2014,"fill":1259,"stroke":1260,"style":2015},"163.5","229.3","93.0",[2017,2066,2068],{"transform":2067},"translate(122.7,176.5) scale(1.000)",[2021,2069],{"d":2070,"fill":2024,"stroke":2071,"style":2026},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[396,2073,2076],{"x":2038,"y":2074,"style":2075},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","built-in styles",[396,2078,2080],{"x":2038,"y":2079,"style":425},"242.5","Good, Bad, Neutral by value",[90,2082],{"x1":2029,"y1":2035,"x2":2029,"y2":2042,"stroke":393,"style":2015},[2044,2084],{"points":2085,"fill":2047},"380.0,161.5 375.0,152.5 385.0,152.5",[374,2087],{"x":2088,"y":2051,"width":2089,"height":2053,"rx":2054,"fill":379,"stroke":386,"style":394},"318.8","122.3",[396,2091,2092],{"x":2029,"y":2057,"style":2058},"yes, people edit it",[374,2094],{"x":2095,"y":2062,"width":2063,"height":2064,"rx":2014,"fill":2096,"stroke":2097,"style":2015},"265.3","#fdefd8","var(--gold,#b4740a)",[2017,2099,2101],{"transform":2100},"translate(368.0,176.5) scale(1.000)",[2021,2102],{"d":2103,"fill":2024,"stroke":2104,"style":2026},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--gold-ink,#7a4e06)",[396,2106,2108],{"x":2029,"y":2074,"style":2107},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","conditional formatting",[396,2110,2111],{"x":2029,"y":2079,"style":425},"colour follows the value",[90,2113],{"x1":2039,"y1":2035,"x2":2039,"y2":2042,"stroke":393,"style":2015},[2044,2115],{"points":2116,"fill":2047},"625.3,161.5 620.3,152.5 630.3,152.5",[374,2118],{"x":2119,"y":2051,"width":2120,"height":2053,"rx":2054,"fill":379,"stroke":386,"style":394},"582.2","86.2",[396,2122,2123],{"x":2039,"y":2057,"style":2058},"house style",[374,2125],{"x":2126,"y":2062,"width":2063,"height":2064,"rx":2014,"fill":392,"stroke":393,"style":2015},"510.7",[2017,2128,2130],{"transform":2129},"translate(613.3,176.5) scale(1.000)",[2021,2131],{"d":2132,"fill":2024,"stroke":2025,"style":2026},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[396,2134,2135],{"x":2039,"y":2074,"style":2030},"custom NamedStyle",[396,2137,2138],{"x":2039,"y":2079,"style":425},"your own theme",[73,2140,2142],{"id":2141},"styles-in-a-larger-report","Styles in a larger report",[10,2144,2145,2146,2148,2149,2151,2152,2154],{},"Built-in styles pay off most in workbooks with several sheets and several authors. A finance team that uses ",[14,2147,39],{}," for assumptions and ",[14,2150,42],{}," for formulas across all its models can open any generated workbook and know immediately which cells are safe to change, without reading documentation. The same convention makes review easier: filtering a sheet by cell colour in Excel, or listing cells by ",[85,2153,1130],{}," in Python, shows every assumption in one pass. Because each cell refers to the style by name rather than carrying its own copy of the formatting, styled workbooks also stay smaller and faster than ones where every cell has individually set fonts and fills — a difference that becomes noticeable on sheets with tens of thousands of formatted cells. And when the house colours change, editing the style once in Excel's gallery restyles every cell that uses it.",[73,2156,2158],{"id":2157},"common-pitfalls","Common pitfalls",[2160,2161,2162,2175,2181,2187,2193],"ul",{},[2163,2164,2165,2169,2170,2172,2173,71],"li",{},[2166,2167,2168],"strong",{},"Excel's spelling of names."," openpyxl uses ",[85,2171,330],{}," and ",[85,2174,341],{},[2163,2176,2177,2180],{},[2166,2178,2179],{},"Expecting styles to update with values."," They are fixed; use conditional formatting for live colours.",[2163,2182,2183,2186],{},[2166,2184,2185],{},"Overriding colours directly."," Override number formats, leave colours to the style.",[2163,2188,2189,2192],{},[2166,2190,2191],{},"Inventing near-duplicates."," Use the built-ins where they fit before defining your own.",[2163,2194,2195,2198,2199,71],{},[2166,2196,2197],{},"A status rule with no tolerance."," Missing a target by 0.1% need not be ",[14,2200,20],{},[73,2202,2204],{"id":2203},"conclusion","Conclusion",[10,2206,2207,2208,2211,2212,2214,2215,2217,2218,2220,2221,29,2223,2225,2226,2228],{},"openpyxl applies Excel's built-in cell styles by name with ",[85,2209,2210],{},"cell.style = \"Good\"",", registering each in the workbook on first use. Use the names in ",[85,2213,276],{}," — ",[85,2216,330],{}," for Heading 1, ",[85,2219,341],{}," for the accent tints — decide ",[14,2222,16],{},[14,2224,24],{}," or ",[14,2227,20],{}," from values in Python, style inputs and calculations so readers know which cells to change, and add only number formats on top. Choose conditional formatting instead when the colours must follow edits.",[73,2230,2232],{"id":2231},"frequently-asked-questions","Frequently asked questions",[10,2234,2235,2238],{},[2166,2236,2237],{},"How do I apply the Good, Bad or Neutral style with openpyxl?","\nAssign the style name to the cell: cell.style = \"Good\". openpyxl adds the built-in style to the workbook the first time it is used.",[10,2240,2241,2244],{},[2166,2242,2243],{},"Why does openpyxl reject \"Heading 1\"?","\nopenpyxl names Excel's Heading 1 to 4 styles Headline 1 to 4; Excel still shows them as Heading 1 to 4 because it identifies them by built-in id.",[10,2246,2247,2250],{},[2166,2248,2249],{},"Can I change the number format of a cell that uses a built-in style?","\nYes. Set cell.number_format after applying the style; the style stays applied and the number format is a direct override.",[10,2252,2253,2256],{},[2166,2254,2255],{},"Should I use built-in styles or conditional formatting for status colours?","\nBuilt-in styles for finished reports where values do not change, conditional formatting when the colour must update as people edit the values.",[73,2258,2260],{"id":2259},"related","Related",[2160,2262,2263,2269,2274,2281],{},[2163,2264,2265,2266,2268],{},"Up one level: ",[67,2267,70],{"href":69}," — cell styling from Python.",[2163,2270,2271,2273],{},[67,2272,120],{"href":119}," — custom named styles.",[2163,2275,2276,2280],{},[67,2277,2279],{"href":2278},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-monthly-kpi-scorecard-with-targets\u002F","Build a Monthly KPI Scorecard with Targets"," — a full KPI report.",[2163,2282,2283,2287],{},[67,2284,2286],{"href":2285},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fcopy-cell-styles-with-openpyxl\u002F","Copy Cell Styles with openpyxl"," — reusing formatting.",[2289,2290,2291],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":83,"searchDepth":158,"depth":158,"links":2293},[2294,2295,2296,2297,2298,2299,2300,2301,2302,2303],{"id":75,"depth":158,"text":76},{"id":124,"depth":158,"text":125},{"id":502,"depth":158,"text":503},{"id":1361,"depth":158,"text":1362},{"id":1644,"depth":158,"text":1645},{"id":2141,"depth":158,"text":2142},{"id":2157,"depth":158,"text":2158},{"id":2203,"depth":158,"text":2204},{"id":2231,"depth":158,"text":2232},{"id":2259,"depth":158,"text":2260},"2026-10-02","Use Excel's own built-in cell styles from Python with openpyxl — Good, Bad and Neutral, Title and the headline styles, Input, Calculation, Total, Note and the accent styles: the exact names openpyxl expects, applying them by value, what happens when you add direct formatting on top, how they appear in Excel's Cell Styles gallery, and when conditional formatting is the better tool.","md",[2308,2310,2312,2314],{"q":2237,"a":2309},"Assign the style name to the cell: cell.style = \"Good\". openpyxl adds the built-in style to the workbook the first time it is used.",{"q":2243,"a":2311},"openpyxl names Excel's Heading 1 to 4 styles Headline 1 to 4; Excel still shows them as Heading 1 to 4 because it identifies them by built-in id.",{"q":2249,"a":2313},"Yes. Set cell.number_format after applying the style; the style stays applied and the number format is a direct override.",{"q":2255,"a":2315},"Built-in styles for finished reports where values do not change, conditional formatting when the colour must update as people edit the values.","apply built-in cell styles good bad neutral openpyxl",null,{"breadcrumb":2319},[2320,2322,2325],{"name":2321,"item":815},"Home",{"name":2323,"item":2324},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":70,"item":69},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral",{"title":2328,"description":2329},"Apply Excel's Built-In Good, Bad and Neutral Styles in Python","openpyxl built-in named styles: Good, Bad, Neutral, Title, Headline 1–4, Input, Calculation, Total, Note and Accent styles, their openpyxl names, applying by value, mixing with direct formatting, and verifying the colours.","apply-built-in-cell-styles-like-good-bad-and-neutral","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-built-in-cell-styles-like-good-bad-and-neutral\u002Findex",[2333,2334],"cell-styling","openpyxl","how-to","Ll1cJvF_JEpJf1QOQCbpnh8AMe9KCngWQvK_Z66sPQs",[2338,2341],{"title":120,"path":2339,"stem":2340,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report\u002Findex",{"title":2342,"path":2343,"stem":2344,"children":-1},"Apply Gradient and Pattern Fills with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-gradient-and-pattern-fills-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-gradient-and-pattern-fills-with-openpyxl\u002Findex",[2346,2348],{"path":2347,"title":2323},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2349,"title":70},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl",[],[2352,2355,2358,2361,2364,2367],{"path":2353,"title":2354,"kind":2335},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter","Write Rich Text with Mixed Formats in One Cell with xlsxwriter",{"path":2356,"title":2357,"kind":2335},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fcopy-cell-styles-with-openpyxl","Copy Cell Styles from One Cell to Another with openpyxl",{"path":2359,"title":2360,"kind":2335},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas","Auto-Fit Column Widths When Writing with pandas",{"path":2362,"title":2363,"kind":2335},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fexport-a-styled-pandas-dataframe-to-excel","Export a Styled pandas DataFrame to Excel",{"path":2365,"title":2366,"kind":2335},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fgenerate-a-staff-rota-in-excel-with-python","Generate a Staff Rota in Excel with Python",{"path":2368,"title":2369,"kind":2335},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",1790972117183]