CINXE.COM
<!doctype html><html lang="en"><head><title data-rh="true">Design System from scratch in Flutter | by Kanan Yusubov | Flutter Community | Nov, 2024 | Medium</title><meta data-rh="true" charset="utf-8"/><meta data-rh="true" name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,maximum-scale=1"/><meta data-rh="true" name="theme-color" content="#000000"/><meta data-rh="true" name="twitter:app:name:iphone" content="Medium"/><meta data-rh="true" name="twitter:app:id:iphone" content="828256236"/><meta data-rh="true" property="al:ios:app_name" content="Medium"/><meta data-rh="true" property="al:ios:app_store_id" content="828256236"/><meta data-rh="true" property="al:android:package" content="com.medium.reader"/><meta data-rh="true" property="fb:app_id" content="542599432471018"/><meta data-rh="true" property="og:site_name" content="Medium"/><meta data-rh="true" property="og:type" content="article"/><meta data-rh="true" property="article:published_time" content="2024-11-06T16:28:41.568Z"/><meta data-rh="true" name="title" content="Design System from scratch in Flutter | by Kanan Yusubov | Flutter Community | Nov, 2024 | Medium"/><meta data-rh="true" property="og:title" content="Design System from scratch in Flutter"/><meta data-rh="true" property="al:android:url" content="medium://p/bc2aebb8bb02"/><meta data-rh="true" property="al:ios:url" content="medium://p/bc2aebb8bb02"/><meta data-rh="true" property="al:android:app_name" content="Medium"/><meta data-rh="true" name="description" content="Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design System, which we have implemented in our projects before. In our…"/><meta data-rh="true" property="og:description" content="Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design…"/><meta data-rh="true" property="og:url" content="https://medium.com/flutter-community/design-system-from-scratch-in-flutter-bc2aebb8bb02"/><meta data-rh="true" property="al:web:url" content="https://medium.com/flutter-community/design-system-from-scratch-in-flutter-bc2aebb8bb02"/><meta data-rh="true" property="og:image" content="https://miro.medium.com/v2/resize:fit:1200/1*CtlNPhgJx2QqxiDGQ8kNNg.png"/><meta data-rh="true" property="article:author" content="https://yusubov.com"/><meta data-rh="true" name="author" content="Kanan Yusubov"/><meta data-rh="true" name="robots" content="index,noarchive,follow,max-image-preview:large"/><meta data-rh="true" name="referrer" content="unsafe-url"/><meta data-rh="true" property="twitter:title" content="Design System from scratch in Flutter"/><meta data-rh="true" name="twitter:site" content="@FlutterComm"/><meta data-rh="true" name="twitter:app:url:iphone" content="medium://p/bc2aebb8bb02"/><meta data-rh="true" property="twitter:description" content="Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design…"/><meta data-rh="true" name="twitter:image:src" content="https://miro.medium.com/v2/resize:fit:1200/1*CtlNPhgJx2QqxiDGQ8kNNg.png"/><meta data-rh="true" name="twitter:card" content="summary_large_image"/><meta data-rh="true" name="twitter:label1" content="Reading time"/><meta data-rh="true" name="twitter:data1" content="17 min read"/><link data-rh="true" rel="icon" href="https://miro.medium.com/v2/5d8de952517e8160e40ef9841c781cdc14a5db313057fa3c3de41c6f5b494b19"/><link data-rh="true" rel="search" type="application/opensearchdescription+xml" title="Medium" href="/osd.xml"/><link data-rh="true" rel="apple-touch-icon" sizes="152x152" href="https://miro.medium.com/v2/resize:fill:304:304/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="120x120" href="https://miro.medium.com/v2/resize:fill:240:240/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="76x76" href="https://miro.medium.com/v2/resize:fill:152:152/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="apple-touch-icon" sizes="60x60" href="https://miro.medium.com/v2/resize:fill:120:120/10fd5c419ac61637245384e7099e131627900034828f4f386bdaa47a74eae156"/><link data-rh="true" rel="mask-icon" href="https://miro.medium.com/v2/resize:fill:1000:1000/7*GAOKVe--MXbEJmV9230oOQ.png" color="#171717"/><link data-rh="true" rel="preconnect" href="https://glyph.medium.com" crossOrigin=""/><link data-rh="true" id="glyph_preload_link" rel="preload" as="style" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" id="glyph_link" rel="stylesheet" type="text/css" href="https://glyph.medium.com/css/unbound.css"/><link data-rh="true" rel="author" href="https://yusubov.com"/><link data-rh="true" rel="canonical" href="https://medium.com/flutter-community/design-system-from-scratch-in-flutter-bc2aebb8bb02"/><link data-rh="true" rel="alternate" href="android-app://com.medium.reader/https/medium.com/p/bc2aebb8bb02"/><script data-rh="true" type="application/ld+json">{"@context":"http:\u002F\u002Fschema.org","@type":"NewsArticle","image":["https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fresize:fit:1200\u002F1*CtlNPhgJx2QqxiDGQ8kNNg.png"],"url":"https:\u002F\u002Fmedium.com\u002Fflutter-community\u002Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02","dateCreated":"2024-11-06T16:28:41.568Z","datePublished":"2024-11-06T16:28:41.568Z","dateModified":"2024-11-13T23:54:33.812Z","headline":"Design System from scratch in Flutter - Flutter Community - Medium","name":"Design System from scratch in Flutter - Flutter Community - Medium","description":"Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design System, which we have implemented in our projects before. In our…","identifier":"bc2aebb8bb02","author":{"@type":"Person","name":"Kanan Yusubov","url":"https:\u002F\u002Fyusubov.com"},"creator":["Kanan Yusubov"],"publisher":{"@type":"Organization","name":"Flutter Community","url":"https:\u002F\u002Fmedium.com\u002Fflutter-community","logo":{"@type":"ImageObject","width":272,"height":60,"url":"https:\u002F\u002Fmiro.medium.com\u002Fv2\u002Fresize:fit:544\u002F7*V1_7XP4snlmqrc_0Njontw.png"}},"mainEntityOfPage":"https:\u002F\u002Fmedium.com\u002Fflutter-community\u002Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02"}</script><style type="text/css" data-fela-rehydration="570" data-fela-type="STATIC">html{box-sizing:border-box;-webkit-text-size-adjust:100%}*, *:before, *:after{box-sizing:inherit}body{margin:0;padding:0;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;color:rgba(0,0,0,0.8);position:relative;min-height:100vh}h1, h2, h3, h4, h5, h6, dl, dd, ol, ul, menu, figure, blockquote, p, pre, form{margin:0}menu, ol, ul{padding:0;list-style:none;list-style-image:none}main{display:block}a{color:inherit;text-decoration:none}a, button, input{-webkit-tap-highlight-color:transparent}img, svg{vertical-align:middle}button{background:transparent;overflow:visible}button, input, optgroup, select, textarea{margin:0}:root{--reach-tabs:1;--reach-menu-button:1}#speechify-root{font-family:Sohne, sans-serif}div[data-popper-reference-hidden="true"]{visibility:hidden;pointer-events:none}.grecaptcha-badge{visibility:hidden} /*XCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>*/.hljs {background: #fff;color: black; }/* Gray DOCTYPE selectors like WebKit */ .xml .hljs-meta {color: #c0c0c0; }.hljs-comment, .hljs-quote {color: #007400; }.hljs-tag, .hljs-attribute, .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-name {color: #aa0d91; }.hljs-variable, .hljs-template-variable {color: #3F6E74; }.hljs-code, .hljs-string, .hljs-meta .hljs-string {color: #c41a16; }.hljs-regexp, .hljs-link {color: #0E0EFF; }.hljs-title, .hljs-symbol, .hljs-bullet, .hljs-number {color: #1c00cf; }.hljs-section, .hljs-meta {color: #643820; }.hljs-title.class_, .hljs-class .hljs-title, .hljs-type, .hljs-built_in, .hljs-params {color: #5c2699; }.hljs-attr {color: #836C28; }.hljs-subst {color: #000; }.hljs-formula {background-color: #eee;font-style: italic; }.hljs-addition {background-color: #baeeba; }.hljs-deletion {background-color: #ffc8bd; }.hljs-selector-id, .hljs-selector-class {color: #9b703f; }.hljs-doctag, .hljs-strong {font-weight: bold; }.hljs-emphasis {font-style: italic; } </style><style type="text/css" data-fela-rehydration="570" data-fela-type="KEYFRAME">@-webkit-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@-moz-keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}@keyframes k1{0%{opacity:0.8}50%{opacity:0.5}100%{opacity:0.8}}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE">.a{font-family:medium-content-sans-serif-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif}.b{font-weight:400}.c{background-color:rgba(255, 255, 255, 1)}.l{display:block}.m{position:sticky}.n{top:0}.o{z-index:500}.p{padding:0 24px}.q{align-items:center}.r{border-bottom:solid 1px #F2F2F2}.y{height:41px}.z{line-height:20px}.ab{display:flex}.ac{height:57px}.ae{flex:1 0 auto}.af{color:inherit}.ag{fill:inherit}.ah{font-size:inherit}.ai{border:inherit}.aj{font-family:inherit}.ak{letter-spacing:inherit}.al{font-weight:inherit}.am{padding:0}.an{margin:0}.ao{cursor:pointer}.ap:disabled{cursor:not-allowed}.aq:disabled{color:#6B6B6B}.ar:disabled{fill:#6B6B6B}.au{width:auto}.av path{fill:#242424}.aw{height:25px}.ax{margin-left:16px}.ay{border:none}.az{border-radius:20px}.ba{width:240px}.bb{background:#F9F9F9}.bc path{fill:#6B6B6B}.be{outline:none}.bf{font-family:sohne, "Helvetica Neue", Helvetica, Arial, sans-serif}.bg{font-size:14px}.bh{width:100%}.bi{padding:10px 20px 10px 0}.bj{background-color:transparent}.bk{color:#242424}.bl::placeholder{color:#6B6B6B}.bm{display:inline-block}.bn{margin-left:12px}.bo{margin-right:12px}.bp{border-radius:4px}.bq{margin-left:24px}.br{height:24px}.bx{background-color:#F9F9F9}.by{border-radius:50%}.bz{height:32px}.ca{width:32px}.cb{justify-content:center}.ch{max-width:680px}.ci{min-width:0}.cj{animation:k1 1.2s ease-in-out infinite}.ck{height:100vh}.cl{margin-bottom:16px}.cm{margin-top:48px}.cn{align-items:flex-start}.co{flex-direction:column}.cp{justify-content:space-between}.cq{margin-bottom:24px}.cw{width:80%}.cx{background-color:#F2F2F2}.dd{height:44px}.de{width:44px}.df{margin:auto 0}.dg{margin-bottom:4px}.dh{height:16px}.di{width:120px}.dj{width:80px}.dp{margin-bottom:8px}.dq{width:96%}.dr{width:98%}.ds{width:81%}.dt{margin-left:8px}.du{color:#6B6B6B}.dv{font-size:13px}.dw{height:100%}.ep{color:#FFFFFF}.eq{fill:#FFFFFF}.er{background:rgba(0, 142, 231, 1)}.es{border-color:rgba(0, 142, 231, 1)}.ew:disabled{cursor:inherit !important}.ex:disabled{opacity:0.3}.ey:disabled:hover{background:rgba(0, 142, 231, 1)}.ez:disabled:hover{border-color:rgba(0, 142, 231, 1)}.fa{border-radius:99em}.fb{border-width:1px}.fc{border-style:solid}.fd{box-sizing:border-box}.fe{text-decoration:none}.ff{text-align:center}.fi{margin-right:32px}.fj{position:relative}.fk{fill:#6B6B6B}.fn{background:transparent}.fo svg{margin-left:4px}.fp svg{fill:#6B6B6B}.fr{box-shadow:inset 0 0 0 1px rgba(0, 0, 0, 0.05)}.fs{position:absolute}.fz{margin:0 24px}.gd{background:rgba(255, 255, 255, 1)}.ge{border:1px solid #F2F2F2}.gf{box-shadow:0 1px 4px #F2F2F2}.gg{max-height:100vh}.gh{overflow-y:auto}.gi{left:0}.gj{top:calc(100vh + 100px)}.gk{bottom:calc(100vh + 100px)}.gl{width:10px}.gm{pointer-events:none}.gx{align-items:baseline}.gy{width:48px}.gz{height:48px}.ha{border:2px solid rgba(255, 255, 255, 1)}.hb{z-index:0}.hc{box-shadow:none}.hd{border:1px solid rgba(0, 0, 0, 0.05)}.he{margin-left:-12px}.hf{width:28px}.hg{height:28px}.hh{z-index:1}.hi{width:24px}.hj{margin-bottom:2px}.hk{flex-wrap:nowrap}.hl{font-size:16px}.hm{line-height:24px}.ho{margin:0 8px}.hp{display:inline}.hq{color:rgba(0, 142, 231, 1)}.hr{fill:rgba(0, 142, 231, 1)}.hu{flex:0 0 auto}.hx{flex-wrap:wrap}.ia{white-space:pre-wrap}.ib{margin-right:4px}.ic{overflow:hidden}.id{max-height:20px}.ie{text-overflow:ellipsis}.if{display:-webkit-box}.ig{-webkit-line-clamp:1}.ih{-webkit-box-orient:vertical}.ii{word-break:break-all}.ik{padding-left:8px}.il{padding-right:8px}.jm> *{flex-shrink:0}.jn{overflow-x:scroll}.jo::-webkit-scrollbar{display:none}.jp{scrollbar-width:none}.jq{-ms-overflow-style:none}.jr{width:74px}.js{flex-direction:row}.jt{z-index:2}.jw{-webkit-user-select:none}.jx{border:0}.jy{fill:rgba(117, 117, 117, 1)}.kb{outline:0}.kc{user-select:none}.kd> svg{pointer-events:none}.km{cursor:progress}.kn{margin-left:4px}.ko{margin-top:0px}.kp{opacity:1}.kq{padding:4px 0}.kt{width:16px}.kv{display:inline-flex}.lb{max-width:100%}.lc{padding:8px 2px}.ld svg{color:#6B6B6B}.lu{word-break:break-word}.lv{word-wrap:break-word}.lw:after{display:block}.lx:after{content:""}.ly:after{clear:both}.lz{margin-left:auto}.ma{margin-right:auto}.mb{max-width:1920px}.mh{clear:both}.mj{cursor:zoom-in}.mk{z-index:auto}.mm{height:auto}.mn{margin-top:32px}.mo{margin-bottom:14px}.mp{padding-top:24px}.mq{padding-bottom:10px}.mr{background-color:#000000}.ms{height:3px}.mt{width:3px}.mu{margin-right:20px}.mv{line-height:1.12}.mw{letter-spacing:-0.022em}.mx{font-style:normal}.my{font-weight:600}.nt{margin-bottom:-0.28em}.nu{line-height:1.58}.nv{letter-spacing:-0.004em}.nw{font-family:source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif}.or{margin-bottom:-0.46em}.os{line-height:1.18}.pg{margin-bottom:-0.31em}.ph{max-width:1432px}.ps{text-decoration:underline}.pt{overflow-x:auto}.pu{font-family:source-code-pro, Menlo, Monaco, "Courier New", Courier, monospace}.pv{padding:32px}.pw{border:1px solid #E5E5E5}.px{line-height:1.4}.py{margin-top:-0.2em}.pz{margin-bottom:-0.2em}.qa{white-space:pre}.qb{min-width:fit-content}.qc{margin-top:16px}.qd{box-shadow:inset 3px 0 0 0 #242424}.qe{padding-left:23px}.qf{margin-left:-20px}.qg{font-style:italic}.qh{font-weight:700}.qi{max-width:820px}.qj{max-width:768px}.qk{margin-top:10px}.ql{max-width:728px}.qo{max-width:1194px}.qp{max-width:480px}.qq{max-width:716px}.qr{list-style-type:disc}.qs{margin-left:30px}.qt{padding-left:0px}.qz{max-width:1840px}.ra{max-width:504px}.rb{margin-bottom:26px}.rc{margin-top:6px}.rd{margin-top:8px}.re{margin-right:8px}.rf{padding:8px 16px}.rg{border-radius:100px}.rh{transition:background 300ms ease}.rj{white-space:nowrap}.rk{border-top:none}.rl{height:52px}.rm{max-height:52px}.rn{box-sizing:content-box}.ro{position:static}.rq{max-width:155px}.sb{height:0px}.sc{margin-bottom:40px}.sd{margin-bottom:48px}.sr{border-radius:2px}.st{height:64px}.su{width:64px}.sv{align-self:flex-end}.sw{flex:1 1 auto}.tc{padding-right:4px}.td{font-weight:500}.tk{color:rgba(255, 255, 255, 1)}.tl{fill:rgba(255, 255, 255, 1)}.tm{background:rgba(25, 25, 25, 1)}.tn{border-color:rgba(25, 25, 25, 1)}.tq:disabled{opacity:0.1}.tr:disabled:hover{background:rgba(25, 25, 25, 1)}.ts:disabled:hover{border-color:rgba(25, 25, 25, 1)}.ub{gap:18px}.uc{fill:rgba(61, 61, 61, 1)}.ue{fill:#242424}.uf{background:0}.ug{border-color:#242424}.uh:disabled:hover{color:#242424}.ui:disabled:hover{fill:#242424}.uj:disabled:hover{border-color:#242424}.uu{border-bottom:solid 1px #E5E5E5}.uv{margin-top:72px}.uw{padding:24px 0}.ux{margin-bottom:0px}.uy{margin-right:16px}.as:hover:not(:disabled){color:rgba(25, 25, 25, 1)}.at:hover:not(:disabled){fill:rgba(25, 25, 25, 1)}.et:hover{background:rgba(28, 122, 192, 1)}.eu:hover{border-color:rgba(28, 122, 192, 1)}.ev:hover{cursor:pointer}.fl:hover{color:#242424}.fm:hover{fill:#242424}.fq:hover svg{fill:#242424}.ft:hover{background-color:rgba(0, 0, 0, 0.1)}.hn:hover{text-decoration:underline}.hs:hover:not(:disabled){color:rgba(28, 122, 192, 1)}.ht:hover:not(:disabled){fill:rgba(28, 122, 192, 1)}.ka:hover{fill:rgba(8, 8, 8, 1)}.kr:hover{fill:#000000}.ks:hover p{color:#000000}.ku:hover{color:#000000}.le:hover svg{color:#000000}.ri:hover{background-color:#F2F2F2}.ss:hover{background-color:none}.to:hover{background:#000000}.tp:hover{border-color:#242424}.ud:hover{fill:rgba(25, 25, 25, 1)}.bd:focus-within path{fill:#242424}.jz:focus{fill:rgba(8, 8, 8, 1)}.lf:focus svg{color:#000000}.ml:focus{transform:scale(1.01)}.ke:active{border-style:none}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (min-width: 1080px)">.d{display:none}.bw{width:64px}.cg{margin:0 64px}.cv{height:48px}.dc{margin-bottom:52px}.do{margin-bottom:48px}.ef{font-size:14px}.eg{line-height:20px}.em{font-size:13px}.eo{padding:5px 12px}.fh{display:flex}.fy{margin-bottom:68px}.gc{max-width:680px}.gr{margin-top:32px}.gw{align-items:center}.iy{border-top:solid 1px #F2F2F2}.iz{border-bottom:solid 1px #F2F2F2}.ja{margin:32px 0 0}.jb{padding:3px 8px}.jk> *{margin-right:24px}.jl> :last-child{margin-right:0}.kl{margin-top:0px}.la{margin:0}.mg{margin-top:40px}.np{font-size:24px}.nq{margin-top:1.25em}.nr{line-height:30px}.ns{letter-spacing:-0.016em}.on{font-size:20px}.oo{margin-top:0.94em}.op{line-height:32px}.oq{letter-spacing:-0.003em}.pd{margin-top:1.72em}.pe{line-height:24px}.pf{letter-spacing:0}.pm{margin-top:56px}.pr{margin-top:2.14em}.qy{margin-top:1.14em}.rv{display:inline-block}.sa{margin-bottom:104px}.se{flex-direction:row}.sh{margin-bottom:0}.si{margin-right:20px}.sx{max-width:500px}.tx{margin-bottom:88px}.ua{margin-bottom:72px}.uo{width:min-width}.ut{padding-top:72px}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (max-width: 1079.98px)">.e{display:none}.kk{margin-top:0px}.qm{margin-left:auto}.qn{text-align:center}.ru{display:inline-block}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (max-width: 903.98px)">.f{display:none}.kj{margin-top:0px}.rt{display:inline-block}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (max-width: 727.98px)">.g{display:none}.kh{margin-top:0px}.ki{margin-right:0px}.rs{display:inline-block}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (max-width: 551.98px)">.h{display:none}.s{display:flex}.t{justify-content:space-between}.bs{width:24px}.cc{margin:0 24px}.cr{height:40px}.cy{margin-bottom:44px}.dk{margin-bottom:32px}.dx{font-size:13px}.dy{line-height:20px}.eh{padding:0px 8px 1px}.fu{margin-bottom:4px}.gn{margin-top:24px}.gs{align-items:flex-start}.hv{flex-direction:column}.hy{margin-bottom:2px}.im{margin:24px -24px 0}.in{padding:0}.jc> *{margin-right:8px}.jd> :last-child{margin-right:24px}.ju{margin-left:0px}.kf{margin-top:0px}.kg{margin-right:0px}.kw{margin:0}.lg{border:1px solid #F2F2F2}.lh{border-radius:99em}.li{padding:0px 16px 0px 12px}.lj{height:38px}.lk{align-items:center}.lm svg{margin-right:8px}.mc{margin-top:32px}.mz{font-size:20px}.na{margin-top:0.93em}.nb{line-height:24px}.nc{letter-spacing:0}.nx{font-size:18px}.ny{margin-top:0.67em}.nz{line-height:28px}.oa{letter-spacing:-0.003em}.ot{font-size:16px}.ou{margin-top:1.23em}.pi{margin-top:40px}.pn{margin-top:1.56em}.qu{margin-top:1.34em}.rr{display:inline-block}.rw{margin-bottom:96px}.sp{margin-bottom:20px}.sq{margin-right:0}.tb{max-width:100%}.te{font-size:24px}.tf{line-height:30px}.tg{letter-spacing:-0.016em}.tt{margin-bottom:64px}.uk{width:100%}.up{padding-top:48px}.ll:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (min-width: 904px) and (max-width: 1079.98px)">.i{display:none}.bv{width:64px}.cf{margin:0 64px}.cu{height:48px}.db{margin-bottom:52px}.dn{margin-bottom:48px}.ed{font-size:14px}.ee{line-height:20px}.ek{font-size:13px}.el{padding:5px 12px}.fg{display:flex}.fx{margin-bottom:68px}.gb{max-width:680px}.gq{margin-top:32px}.gv{align-items:center}.iu{border-top:solid 1px #F2F2F2}.iv{border-bottom:solid 1px #F2F2F2}.iw{margin:32px 0 0}.ix{padding:3px 8px}.ji> *{margin-right:24px}.jj> :last-child{margin-right:0}.kz{margin:0}.mf{margin-top:40px}.nl{font-size:24px}.nm{margin-top:1.25em}.nn{line-height:30px}.no{letter-spacing:-0.016em}.oj{font-size:20px}.ok{margin-top:0.94em}.ol{line-height:32px}.om{letter-spacing:-0.003em}.pa{margin-top:1.72em}.pb{line-height:24px}.pc{letter-spacing:0}.pl{margin-top:56px}.pq{margin-top:2.14em}.qx{margin-top:1.14em}.rz{margin-bottom:104px}.sf{flex-direction:row}.sj{margin-bottom:0}.sk{margin-right:20px}.sy{max-width:500px}.tw{margin-bottom:88px}.tz{margin-bottom:72px}.un{width:min-width}.us{padding-top:72px}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (min-width: 728px) and (max-width: 903.98px)">.j{display:none}.w{display:flex}.x{justify-content:space-between}.bu{width:64px}.ce{margin:0 48px}.ct{height:48px}.da{margin-bottom:52px}.dm{margin-bottom:48px}.eb{font-size:13px}.ec{line-height:20px}.ej{padding:0px 8px 1px}.fw{margin-bottom:68px}.ga{max-width:680px}.gp{margin-top:32px}.gu{align-items:center}.iq{border-top:solid 1px #F2F2F2}.ir{border-bottom:solid 1px #F2F2F2}.is{margin:32px 0 0}.it{padding:3px 8px}.jg> *{margin-right:24px}.jh> :last-child{margin-right:0}.ky{margin:0}.me{margin-top:40px}.nh{font-size:24px}.ni{margin-top:1.25em}.nj{line-height:30px}.nk{letter-spacing:-0.016em}.of{font-size:20px}.og{margin-top:0.94em}.oh{line-height:32px}.oi{letter-spacing:-0.003em}.ox{margin-top:1.72em}.oy{line-height:24px}.oz{letter-spacing:0}.pk{margin-top:56px}.pp{margin-top:2.14em}.qw{margin-top:1.14em}.ry{margin-bottom:104px}.sg{flex-direction:row}.sl{margin-bottom:0}.sm{margin-right:20px}.sz{max-width:500px}.tv{margin-bottom:88px}.ty{margin-bottom:72px}.um{width:min-width}.ur{padding-top:72px}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="all and (min-width: 552px) and (max-width: 727.98px)">.k{display:none}.u{display:flex}.v{justify-content:space-between}.bt{width:24px}.cd{margin:0 24px}.cs{height:40px}.cz{margin-bottom:44px}.dl{margin-bottom:32px}.dz{font-size:13px}.ea{line-height:20px}.ei{padding:0px 8px 1px}.fv{margin-bottom:4px}.go{margin-top:24px}.gt{align-items:flex-start}.hw{flex-direction:column}.hz{margin-bottom:2px}.io{margin:24px 0 0}.ip{padding:0}.je> *{margin-right:8px}.jf> :last-child{margin-right:8px}.jv{margin-left:0px}.kx{margin:0}.ln{border:1px solid #F2F2F2}.lo{border-radius:99em}.lp{padding:0px 16px 0px 12px}.lq{height:38px}.lr{align-items:center}.lt svg{margin-right:8px}.md{margin-top:32px}.nd{font-size:20px}.ne{margin-top:0.93em}.nf{line-height:24px}.ng{letter-spacing:0}.ob{font-size:18px}.oc{margin-top:0.67em}.od{line-height:28px}.oe{letter-spacing:-0.003em}.ov{font-size:16px}.ow{margin-top:1.23em}.pj{margin-top:40px}.po{margin-top:1.56em}.qv{margin-top:1.34em}.rx{margin-bottom:96px}.sn{margin-bottom:20px}.so{margin-right:0}.ta{max-width:100%}.th{font-size:24px}.ti{line-height:30px}.tj{letter-spacing:-0.016em}.tu{margin-bottom:64px}.ul{width:100%}.uq{padding-top:48px}.ls:hover{border-color:#E5E5E5}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="print">.rp{display:none}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="(orientation: landscape) and (max-width: 903.98px)">.ij{max-height:none}</style><style type="text/css" data-fela-rehydration="570" data-fela-type="RULE" media="(prefers-reduced-motion: no-preference)">.mi{transition:transform 300ms cubic-bezier(0.2, 0, 0.2, 1)}</style></head><body><div id="root"><div class="a b c"><div class="d e f g h i j k"></div><script>document.domain = document.domain;</script><div class="l c"><div class="l m n o c"><div class="p q r s t u v w x i d y z"><a class="du ag dv bf ak b am an ao ap aq ar as at s u w i d q dw z" href="https://rsci.app.link/?%24canonical_url=https%3A%2F%2Fmedium.com%2Fp%2Fbc2aebb8bb02&%7Efeature=LoOpenInAppButton&%7Echannel=ShowPostUnderCollection&source=---top_nav_layout_nav----------------------------------" rel="noopener follow">Open in app<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="none" viewBox="0 0 10 10" class="dt"><path fill="currentColor" d="M.985 8.485a.375.375 0 1 0 .53.53zM8.75 1.25h.375A.375.375 0 0 0 8.75.875zM8.375 6.5a.375.375 0 1 0 .75 0zM3.5.875a.375.375 0 1 0 0 .75zm-1.985 8.14 7.5-7.5-.53-.53-7.5 7.5zm6.86-7.765V6.5h.75V1.25zM3.5 1.625h5.25v-.75H3.5z"></path></svg></a><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&source=post_page---top_nav_layout_nav-----------------------global_nav-----------">Sign in</a></span></p></div></div></div><div class="p q r ab ac"><div class="ab q ae"><a class="af ag ah ai aj ak al am an ao ap aq ar as at ab" aria-label="Homepage" data-testid="headerMediumLogo" rel="noopener follow" href="/?source=---top_nav_layout_nav----------------------------------"><svg xmlns="http://www.w3.org/2000/svg" width="719" height="160" fill="none" viewBox="0 0 719 160" class="au av aw"><path fill="#242424" d="m174.104 9.734.215-.047V8.02H130.39L89.6 103.89 48.81 8.021H1.472v1.666l.212.047c8.018 1.81 12.09 4.509 12.09 14.242V137.93c0 9.734-4.087 12.433-12.106 14.243l-.212.047v1.671h32.118v-1.665l-.213-.048c-8.018-1.809-12.089-4.509-12.089-14.242V30.586l52.399 123.305h2.972l53.925-126.743V140.75c-.687 7.688-4.721 10.062-11.982 11.701l-.215.05v1.652h55.948v-1.652l-.215-.05c-7.269-1.639-11.4-4.013-12.087-11.701l-.037-116.774h.037c0-9.733 4.071-12.432 12.087-14.242m25.555 75.488c.915-20.474 8.268-35.252 20.606-35.507 3.806.063 6.998 1.312 9.479 3.714 5.272 5.118 7.751 15.812 7.368 31.793zm-.553 5.77h65.573v-.275c-.186-15.656-4.721-27.834-13.466-36.196-7.559-7.227-18.751-11.203-30.507-11.203h-.263c-6.101 0-13.584 1.48-18.909 4.16-6.061 2.807-11.407 7.003-15.855 12.511-7.161 8.874-11.499 20.866-12.554 34.343q-.05.606-.092 1.212a50 50 0 0 0-.065 1.151 85.807 85.807 0 0 0-.094 5.689c.71 30.524 17.198 54.917 46.483 54.917 25.705 0 40.675-18.791 44.407-44.013l-1.886-.664c-6.557 13.556-18.334 21.771-31.738 20.769-18.297-1.369-32.314-19.922-31.042-42.395m139.722 41.359c-2.151 5.101-6.639 7.908-12.653 7.908s-11.513-4.129-15.418-11.63c-4.197-8.053-6.405-19.436-6.405-32.92 0-28.067 8.729-46.22 22.24-46.22 5.657 0 10.111 2.807 12.236 7.704zm43.499 20.008c-8.019-1.897-12.089-4.722-12.089-14.951V1.309l-48.716 14.353v1.757l.299-.024c6.72-.543 11.278.386 13.925 2.83 2.072 1.915 3.082 4.853 3.082 8.987v18.66c-4.803-3.067-10.516-4.56-17.448-4.56-14.059 0-26.909 5.92-36.176 16.672-9.66 11.205-14.767 26.518-14.767 44.278-.003 31.72 15.612 53.039 38.851 53.039 13.595 0 24.533-7.449 29.54-20.013v16.865h43.711v-1.746zM424.1 19.819c0-9.904-7.468-17.374-17.375-17.374-9.859 0-17.573 7.632-17.573 17.374s7.721 17.374 17.573 17.374c9.907 0 17.375-7.47 17.375-17.374m11.499 132.546c-8.019-1.897-12.089-4.722-12.089-14.951h-.035V43.635l-43.714 12.551v1.705l.263.024c9.458.842 12.047 4.1 12.047 15.152v81.086h43.751v-1.746zm112.013 0c-8.018-1.897-12.089-4.722-12.089-14.951V43.635l-41.621 12.137v1.71l.246.026c7.733.813 9.967 4.257 9.967 15.36v59.279c-2.578 5.102-7.415 8.131-13.274 8.336-9.503 0-14.736-6.419-14.736-18.073V43.638l-43.714 12.55v1.703l.262.024c9.459.84 12.05 4.097 12.05 15.152v50.17a56.3 56.3 0 0 0 .91 10.444l.787 3.423c3.701 13.262 13.398 20.197 28.59 20.197 12.868 0 24.147-7.966 29.115-20.43v17.311h43.714v-1.747zm169.818 1.788v-1.749l-.213-.05c-8.7-2.006-12.089-5.789-12.089-13.49v-63.79c0-19.89-11.171-31.761-29.883-31.761-13.64 0-25.141 7.882-29.569 20.16-3.517-13.01-13.639-20.16-28.606-20.16-13.146 0-23.449 6.938-27.869 18.657V43.643L545.487 55.68v1.715l.263.024c9.345.829 12.047 4.181 12.047 14.95v81.784h40.787v-1.746l-.215-.053c-6.941-1.631-9.181-4.606-9.181-12.239V66.998c1.836-4.289 5.537-9.37 12.853-9.37 9.086 0 13.692 6.296 13.692 18.697v77.828h40.797v-1.746l-.215-.053c-6.94-1.631-9.18-4.606-9.18-12.239V75.066a42 42 0 0 0-.578-7.26c1.947-4.661 5.86-10.177 13.475-10.177 9.214 0 13.691 6.114 13.691 18.696v77.828z"></path></svg></a><div class="ax h"><div class="ab ay az ba bb q bc bd"><div class="bm" aria-hidden="false" aria-describedby="searchResults" aria-labelledby="searchResults"></div><div class="bn bo ab"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div><input role="combobox" aria-controls="searchResults" aria-expanded="false" aria-label="search" data-testid="headerSearchInput" tabindex="0" class="ay be bf bg z bh bi bj bk bl" placeholder="Search" value=""/></div></div></div><div class="h k w fg fh"><div class="fi ab"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerWriteButton" rel="noopener follow" href="/m/signin?operation=register&redirect=https%3A%2F%2Fmedium.com%2Fnew-story&source=---top_nav_layout_nav-----------------------new_post_topnav-----------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Write"><path fill="currentColor" d="M14 4a.5.5 0 0 0 0-1zm7 6a.5.5 0 0 0-1 0zm-7-7H4v1h10zM3 4v16h1V4zm1 17h16v-1H4zm17-1V10h-1v10zm-1 1a1 1 0 0 0 1-1h-1zM3 20a1 1 0 0 0 1 1v-1zM4 3a1 1 0 0 0-1 1h1z"></path><path stroke="currentColor" d="m17.5 4.5-8.458 8.458a.25.25 0 0 0-.06.098l-.824 2.47a.25.25 0 0 0 .316.316l2.47-.823a.25.25 0 0 0 .098-.06L19.5 6.5m-2-2 2.323-2.323a.25.25 0 0 1 .354 0l1.646 1.646a.25.25 0 0 1 0 .354L19.5 6.5m-2-2 2 2"></path></svg><div class="dt l">Write</div></div></a></span></div></div><div class="k j i d"><div class="fi ab"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSearchButton" rel="noopener follow" href="/search?source=---top_nav_layout_nav----------------------------------"><div class="bf b bg z du fj fk ab q fl fm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" aria-label="Search"><path fill="currentColor" fill-rule="evenodd" d="M4.092 11.06a6.95 6.95 0 1 1 13.9 0 6.95 6.95 0 0 1-13.9 0m6.95-8.05a8.05 8.05 0 1 0 5.13 14.26l3.75 3.75a.56.56 0 1 0 .79-.79l-3.73-3.73A8.05 8.05 0 0 0 11.042 3z" clip-rule="evenodd"></path></svg></div></a></div></div><div class="fi h k j"><div class="ab q"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><button class="bf b dx dy eh dz ea ei eb ec ej ek ee el em eg eo ep eq er es et eu ev ew ex ey ez fa fb fc fd bm fe ff" data-testid="headerSignUpButton">Sign up</button></span></p><div class="ax l"><p class="bf b dx dy dz ea eb ec ed ee ef eg du"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerSignInButton" rel="noopener follow" href="/m/signin?operation=login&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&source=post_page---top_nav_layout_nav-----------------------global_nav-----------">Sign in</a></span></p></div></div></div><div class="l" aria-hidden="false"><button class="ay fn am ab q ao fo fp fq" aria-label="user options menu" data-testid="headerUserIcon"><div class="l fj"><img alt="" class="l fd by bz ca cx" src="https://miro.medium.com/v2/resize:fill:64:64/1*dmbNkD5D-u45r44go_cf0g.png" width="32" height="32" loading="lazy" role="presentation"/><div class="fr by l bz ca fs n ay ft"></div></div></button></div></div></div><div class="l"><div class="fu fv fw fx fy l"><div class="ab cb"><div class="ci bh fz ga gb gc"></div></div><article><div class="l"><div class="l"><span class="l"></span><section><div><div class="fs gi gj gk gl gm"></div><div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="speechify-ignore gn go gp gq gr l"><div><div class="speechify-ignore ab cp"><div class="speechify-ignore bh l"><div class="gs gt gu gv gw ab"><div><div class="ab gx"><div><div class="bm" aria-hidden="false"><a rel="noopener follow" href="/@thisisyusub?source=post_page---byline--bc2aebb8bb02--------------------------------"><div class="l gy gz by ha hb"><div class="l fj"><img alt="Kanan Yusubov" class="l fd by dd de cx" src="https://miro.medium.com/v2/resize:fill:88:88/1*5aYm6yzxghzIfC3A6s2_gA.jpeg" width="44" height="44" loading="lazy" data-testid="authorPhoto"/><div class="hc by l dd de fs n hd ft"></div></div></div></a></div></div><div class="he ab fj"><div><div class="bm" aria-hidden="false"><a href="https://medium.com/flutter-community?source=post_page---byline--bc2aebb8bb02--------------------------------" rel="noopener follow"><div class="l hf hg by ha hh"><div class="l fj"><img alt="Flutter Community" class="l fd by br hi cx" src="https://miro.medium.com/v2/resize:fill:48:48/1*nE4OFcqk2kx2-Lzhey8QKA.png" width="24" height="24" loading="lazy" data-testid="publicationPhoto"/><div class="hc by l br hi fs n hd ft"></div></div></div></a></div></div></div></div></div><div class="bn bh l"><div class="ab"><div style="flex:1"><span class="bf b bg z bk"><div class="hj ab q"><div class="ab q hk"><div class="ab q"><div><div class="bm" aria-hidden="false"><p class="bf b hl hm bk"><a class="af ag ah ai aj ak al am an ao ap aq ar hn" data-testid="authorName" rel="noopener follow" href="/@thisisyusub?source=post_page---byline--bc2aebb8bb02--------------------------------">Kanan Yusubov</a></p></div></div></div><span class="ho hp" aria-hidden="true"><span class="bf b bg z du">·</span></span><p class="bf b hl hm du"><span><a class="hq hr ah ai aj ak al am an ao ap aq ar ex hs ht" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2F571559932209&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&user=Kanan+Yusubov&userId=571559932209&source=post_page-571559932209--byline--bc2aebb8bb02---------------------post_header-----------">Follow</a></span></p></div></div></span></div></div><div class="l hu"><span class="bf b bg z du"><div class="ab cn hv hw hx"><div class="hy hz ab"><div class="bf b bg z du ab ia"><span class="ib l hu">Published in</span><div><div class="l" aria-hidden="false"><a class="af ag ah ai aj ak al am an ao ap aq ar hn ab q" data-testid="publicationName" href="https://medium.com/flutter-community?source=post_page---byline--bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b bg z ic id ie if ig ih ii ij bk">Flutter Community</p></a></div></div></div><div class="h k"><span class="ho hp" aria-hidden="true"><span class="bf b bg z du">·</span></span></div></div><span class="bf b bg z du"><div class="ab ae"><span data-testid="storyReadTime">17 min read</span><div class="ik il l" aria-hidden="true"><span class="l" aria-hidden="true"><span class="bf b bg z du">·</span></span></div><span data-testid="storyPublishDate">Nov 6, 2024</span></div></span></div></span></div></div></div><div class="ab cp im in io ip iq ir is it iu iv iw ix iy iz ja jb"><div class="h k w fg fh q"><div class="jr l"><div class="ab q js jt"><div class="pw-multi-vote-icon fj ib ju jv jw"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter-community%2Fbc2aebb8bb02&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&user=Kanan+Yusubov&userId=571559932209&source=---header_actions--bc2aebb8bb02---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="jx ao jy jz ka kb am kc kd ke jw"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l kf kg kh ki kj kk kl"><p class="bf b dv z du"><span class="km">--</span></p></div></div></div><div><div class="bm" aria-hidden="false"><button class="ao jx kp kq ab q fk kr ks" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="ko"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b dv z du"><span class="pw-responses-count kn ko">6</span></p></button></div></div></div><div class="ab q jc jd je jf jg jh ji jj jk jl jm jn jo jp jq"><div class="kt k j i d"></div><div class="h k"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="headerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fbc2aebb8bb02&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&source=---header_actions--bc2aebb8bb02---------------------bookmark_footer-----------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du ku" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="fd kv cn"><div class="l ae"><div class="ab cb"><div class="kw kx ky kz la lb ci bh"><div class="ab"><div class="bm bh" aria-hidden="false"><div><div class="bm" aria-hidden="false"><button aria-label="Listen" data-testid="audioPlayButton" class="af fk ah ai aj ak al lc an ao ap ex ld le ks lf lg lh li lj s lk ll lm ln lo lp lq u lr ls lt"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M3 12a9 9 0 1 1 18 0 9 9 0 0 1-18 0m9-10C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m3.376 10.416-4.599 3.066a.5.5 0 0 1-.777-.416V8.934a.5.5 0 0 1 .777-.416l4.599 3.066a.5.5 0 0 1 0 .832" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Listen</p></div></button></div></div></div></div></div></div></div></div><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="headerSocialShareButton" class="af fk ah ai aj ak al lc an ao ap ex ld le ks lf lg lh li lj s lk ll lm ln lo lp lq u lr ls lt"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg><div class="j i d"><p class="bf b bg z du">Share</p></div></button></div></div></div></div></div></div></div></div></div></div></div></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><figure class="mc md me mf mg mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma mb"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*CtlNPhgJx2QqxiDGQ8kNNg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*CtlNPhgJx2QqxiDGQ8kNNg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*CtlNPhgJx2QqxiDGQ8kNNg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*CtlNPhgJx2QqxiDGQ8kNNg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*CtlNPhgJx2QqxiDGQ8kNNg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*CtlNPhgJx2QqxiDGQ8kNNg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*CtlNPhgJx2QqxiDGQ8kNNg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*CtlNPhgJx2QqxiDGQ8kNNg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="394" loading="eager" role="presentation"/></picture></div></div></figure></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h1 id="4d22" class="mv mw mx bf my mz na nb nc nd ne nf ng nh ni nj nk nl nm nn no np nq nr ns nt bk">Design System from scratch in Flutter</h1><p id="c22b" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design System, which we have implemented in our projects before.</p></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="1d81" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Why do we need a Design System?</h2><p id="b947" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">In our example, we have applied it to share our design code between mobile, web, and desktop apps. As a result, it is an independent package working on its own. We can inject into any project in a few steps.</p></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="b9e2" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Let’s start with the atomic parts.</h2><p id="68d9" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">As a first step, we divided all minor parts — colors, radiuses, shadows, etc into independent classes. Definitely, Implemented design system codes depend on the designer’s implementation.</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma ph"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*zUbtS5Uz9TkVtGabxZI4aA.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*zUbtS5Uz9TkVtGabxZI4aA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*zUbtS5Uz9TkVtGabxZI4aA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*zUbtS5Uz9TkVtGabxZI4aA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*zUbtS5Uz9TkVtGabxZI4aA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*zUbtS5Uz9TkVtGabxZI4aA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*zUbtS5Uz9TkVtGabxZI4aA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*zUbtS5Uz9TkVtGabxZI4aA.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="279" loading="eager" role="presentation"/></picture></div></div></figure><p id="0d36" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">As you can see, the designer divided atomic parts for us (thanks to our <a class="af ps" href="https://www.linkedin.com/in/gadirli/" rel="noopener ugc nofollow" target="_blank">designer</a>)</p><p id="8847" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">I will not mention all the code; it will be snippets. They are like the following examples:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="3bd9" class="px mw mx pu b bg py pz l qa qb">/// {@template app_colors}<br/>/// Colors class for themes which provides direct access with static fields.<br/>/// {@endtemplate}<br/>class AppColors {<br/> AppColors._();<br/><br/> /// The color white<br/> static const white = Colors.white;<br/><br/> /// The color black<br/> static const black = Colors.black;<br/><br/> /// The color transparent<br/> static const transparent = Colors.transparent;<br/><br/> /// Brand color palette.<br/> static const brand = MaterialColor(<br/> 0xFF347AF6,<br/> {<br/> 50: Color(0xFFF0F5FF),<br/> 100: Color(0xFFE0ECFF),<br/> 150: Color(0xFFD3E1FB),<br/> 200: Color(0xFFBDD3F9),<br/> 250: Color(0xFF9FBFF9),<br/> 300: Color(0xFF81ACF9),<br/> 400: Color(0xFF5A93F9),<br/> 500: Color(0xFF347AF6),<br/> 600: Color(0xFF1559D1),<br/> 700: Color(0xFF174EAF),<br/> 800: Color(0xFF1D4387),<br/> 900: Color(0xFF163367),<br/> },<br/> );<br/><br/> /// Light gray color palette.<br/> static const grayLight = MaterialColor(<br/> 0xFF667085,<br/> {<br/> 50: Color(0xFFFCFCFD),<br/> 100: Color(0xFFF9FAFB),<br/> 150: Color(0xFFF2F4F7),<br/> 200: Color(0xFFEAECF0),<br/> 250: Color(0xFFD0D5DD),<br/> 300: Color(0xFF98A2B3),<br/> 400: Color(0xFF667085),<br/> 500: Color(0xFF475467),<br/> 600: Color(0xFF344054),<br/> 700: Color(0xFF182230),<br/> 800: Color(0xFF101828),<br/> 900: Color(0xFF0C111D),<br/> },<br/> );<br/><br/> /// Dark gray color palette.<br/> static const grayDark = MaterialColor(<br/> 0xFF85888E,<br/> {<br/> 50: Color(0xFFFAFAFA),<br/> 100: Color(0xFFF5F5F6),<br/> 150: Color(0xFFF0F1F1),<br/> 200: Color(0xFFECECED),<br/> 250: Color(0xFFCECFD2),<br/> 300: Color(0xFF94969C),<br/> 400: Color(0xFF85888E),<br/> 500: Color(0xFF61646C),<br/> 600: Color(0xFF333741),<br/> 700: Color(0xFF1F242F),<br/> 800: Color(0xFF161B26),<br/> 900: Color(0xFF0C111D),<br/> },<br/> );</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="7d64" class="px mw mx pu b bg py pz l qa qb">/// {@template app_radius}<br/>/// Radius class contains all radius used in app<br/>/// {@endtemplate}<br/>class AppRadius {<br/> AppRadius._();<br/><br/> /// Radius of 0.<br/> static const none = Radius.zero;<br/><br/> /// Extra extra small radius of 2.<br/> static const xxs = Radius.circular(2);<br/><br/> /// Extra small radius of 4.<br/> static const xs = Radius.circular(4);<br/><br/> /// Small radius of 6.<br/> static const sm = Radius.circular(6);<br/>}</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="ef47" class="px mw mx pu b bg py pz l qa qb">/// {@template app_shadow}<br/>/// Shadow class contains all shadows used in app<br/>/// {@endtemplate}<br/>class AppShadow {<br/> AppShadow._();<br/><br/><br/> /// Extra small shadow.<br/> static const xs = [<br/> BoxShadow(<br/> blurRadius: 2,<br/> offset: Offset(0, 1),<br/> color: Color.fromRGBO(16, 24, 40, 0.05),<br/> ),<br/> ];<br/><br/> /// Small shadow.<br/> static const sm = [<br/> BoxShadow(<br/> color: Color(0x0F101828),<br/> blurRadius: 2,<br/> offset: Offset(0, 1),<br/> ),<br/> BoxShadow(<br/> color: Color(0x19101828),<br/> blurRadius: 3,<br/> offset: Offset(0, 1),<br/> ),<br/> ];<br/>}</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="d4c2" class="px mw mx pu b bg py pz l qa qb">/// {@template app_spacing}<br/>/// Class contains all space (does not matter is it vertical<br/>/// or horizontal used in app<br/>/// {@endtemplate}<br/>class AppSpacing {<br/> AppSpacing._();<br/><br/> /// No spacing.<br/> static const none = 0.0;<br/><br/> /// Extra extra small spacing of 2.0.<br/> static const xxs = 2.0;<br/><br/> /// Extra small spacing of 4.0.<br/> static const xs = 4.0;<br/><br/> /// Small spacing of 6.0.<br/> static const sm = 6.0;</span></pre><blockquote class="qd qe qf"><p id="01c7" class="nu nv qg nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk"><strong class="nw qh">You can have more atomic parts in your projects.</strong></p></blockquote></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="87b5" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Next stage — components</h2><p id="e4bf" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">I will show you some of our components — like buttons, textfield. Other components developed in the same way.</p></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="6b11" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Theme development for component</h2><p id="2d41" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">I have written a new theme class for each of our components to keep them more clean. <strong class="nw qh"><em class="qg">Sure, it is provided by our designer, too :)</em></strong></p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma qi"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*X_JQ7ICywiR7skrp8vMNUg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*X_JQ7ICywiR7skrp8vMNUg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*X_JQ7ICywiR7skrp8vMNUg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*X_JQ7ICywiR7skrp8vMNUg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*X_JQ7ICywiR7skrp8vMNUg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*X_JQ7ICywiR7skrp8vMNUg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*X_JQ7ICywiR7skrp8vMNUg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*X_JQ7ICywiR7skrp8vMNUg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="451" loading="eager" role="presentation"/></picture></div></div></figure><blockquote class="qd qe qf"><p id="5ca7" class="nu nv qg nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Keep in mind that Theme classes are extended from <strong class="nw qh">ThemeExtension</strong>, in this way, we can register them as theme extensions and use them with Theme class.</p></blockquote><p id="3958" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">For instance, we can check Theme classes for buttons and text fields:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="6557" class="px mw mx pu b bg py pz l qa qb">/// {@template app_button_theme}<br/>/// Theme class which provides configuration of buttons<br/>/// {@endtemplate}<br/>class AppButtonTheme extends ThemeExtension<AppButtonTheme> {<br/> /// {@macro app_button_theme}<br/> const AppButtonTheme({<br/> required this.primaryText,<br/> required this.primaryDefault,<br/> required this.primaryHover,<br/> required this.primaryFocused,<br/> });<br/><br/> /// {@macro app_button_theme}<br/> factory AppButtonTheme.light() {<br/> return AppButtonTheme(<br/> primaryText: AppColors.white,<br/> primaryDefault: AppColors.brand.shade500,<br/> primaryHover: AppColors.brand.shade600,<br/> primaryFocused: AppColors.brand.shade700,<br/> );<br/> }<br/><br/> /// The color of the primary text.<br/> final Color primaryText;<br/><br/> /// The color of the primary button default.<br/> final Color primaryDefault;<br/><br/> /// The color of the primary button hover.<br/> final Color primaryHover;<br/><br/> /// The color of the primary button focused.<br/> final Color primaryFocused;<br/><br/> @override<br/> ThemeExtension<AppButtonTheme> copyWith({<br/> Color? primaryText,<br/> Color? primaryDefault,<br/> Color? primaryHover,<br/> Color? primaryFocused,<br/> }) {<br/> return AppButtonTheme(<br/> primaryText: primaryText ?? this.primaryText,<br/> primaryDefault: primaryDefault ?? this.primaryDefault,<br/> primaryHover: primaryHover ?? this.primaryHover,<br/> primaryFocused: primaryFocused ?? this.primaryFocused,<br/> );<br/> }<br/><br/> @override<br/> ThemeExtension<AppButtonTheme> lerp(<br/> covariant ThemeExtension<AppButtonTheme>? other,<br/> double t,<br/> ) {<br/> if (other is! AppButtonTheme) {<br/> return this;<br/> }<br/><br/> return AppButtonTheme(<br/> primaryText: Color.lerp(primaryText, other.primaryText, t)!,<br/> primaryDefault: Color.lerp(primaryDefault, other.primaryDefault, t)!,<br/> primaryHover: Color.lerp(primaryHover, other.primaryHover, t)!,<br/> primaryFocused: Color.lerp(primaryFocused, other.primaryFocused, t)!,<br/> );<br/> }<br/>}</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="897f" class="px mw mx pu b bg py pz l qa qb">/// {@template app_input_theme}<br/>/// Theme class which provides configuration of [AppTextField]<br/>/// {@endtemplate}<br/>class AppInputTheme extends ThemeExtension<AppInputTheme> {<br/> /// {@macro app_input_theme}<br/> const AppInputTheme({<br/> required this.defaultText,<br/> required this.focusedOnBrand,<br/> required this.focusedTextDefault,<br/> required this.errorTextDefault,<br/> required this.successTextDefault,<br/> required this.disabledText,<br/> required this.borderDefault,<br/> required this.borderHover,<br/> required this.borderFocused,<br/> required this.borderError,<br/> required this.borderSuccess,<br/> required this.borderDisabled,<br/> required this.defaultColor,<br/> required this.disabledColor,<br/> });<br/><br/> /// {@macro app_input_theme}<br/> factory AppInputTheme.light() {<br/> return AppInputTheme(<br/> defaultText: AppColors.grayLight.shade400,<br/> focusedOnBrand: AppColors.brand.shade500,<br/> focusedTextDefault: AppColors.grayLight.shade600,<br/> errorTextDefault: AppColors.error.shade400,<br/> successTextDefault: AppColors.success.shade400,<br/> disabledText: AppColors.grayLight[250]!,<br/> borderDefault: AppColors.grayLight[250]!,<br/> borderHover: AppColors.grayLight.shade300,<br/> borderFocused: AppColors.brand.shade500,<br/> borderError: AppColors.error.shade400,<br/> borderSuccess: AppColors.success.shade400,<br/> borderDisabled: AppColors.grayLight.shade200,<br/> defaultColor: AppColors.white,<br/> disabledColor: AppColors.grayLight.shade100,<br/> );<br/> }<br/><br/> /// The default text color.<br/> final Color defaultText;<br/><br/> /// The text color when focused on brand.<br/> final Color focusedOnBrand;<br/><br/> /// The text color when focused.<br/> final Color focusedTextDefault;<br/><br/> /// The text color when error.<br/> final Color errorTextDefault;<br/><br/> /// The text color when success.<br/> final Color successTextDefault;<br/><br/> /// The text color when disabled.<br/> final Color disabledText;<br/><br/> /// The default border color.<br/> final Color borderDefault;<br/><br/> /// The border color when hovered.<br/> final Color borderHover;<br/><br/> /// The border color when focused.<br/> final Color borderFocused;<br/><br/> /// The border color when error.<br/> final Color borderError;<br/><br/> /// The border color when success.<br/> final Color borderSuccess;<br/><br/> /// The border color when disabled.<br/> final Color borderDisabled;<br/><br/> /// The default color.<br/> final Color defaultColor;<br/><br/> /// The disabled color.<br/> final Color disabledColor;<br/><br/> @override<br/> ThemeExtension<AppInputTheme> copyWith({<br/> Color? defaultText,<br/> Color? focusedOnBrand,<br/> Color? focusedTextDefault,<br/> Color? errorTextDefault,<br/> Color? successTextDefault,<br/> Color? disabledText,<br/> Color? borderDefault,<br/> Color? borderHover,<br/> Color? borderFocused,<br/> Color? borderError,<br/> Color? borderSuccess,<br/> Color? borderDisabled,<br/> Color? defaultColor,<br/> Color? disabledColor,<br/> }) {<br/> return AppInputTheme(<br/> defaultText: defaultText ?? this.defaultText,<br/> focusedOnBrand: focusedOnBrand ?? this.focusedOnBrand,<br/> focusedTextDefault: focusedTextDefault ?? this.focusedTextDefault,<br/> errorTextDefault: errorTextDefault ?? this.errorTextDefault,<br/> successTextDefault: successTextDefault ?? this.successTextDefault,<br/> disabledText: disabledText ?? this.disabledText,<br/> borderDefault: borderDefault ?? this.borderDefault,<br/> borderHover: borderHover ?? this.borderHover,<br/> borderFocused: borderFocused ?? this.borderFocused,<br/> borderError: borderError ?? this.borderError,<br/> borderSuccess: borderSuccess ?? this.borderSuccess,<br/> borderDisabled: borderDisabled ?? this.borderDisabled,<br/> defaultColor: defaultColor ?? this.defaultColor,<br/> disabledColor: disabledColor ?? this.disabledColor,<br/> );<br/> }<br/><br/> @override<br/> ThemeExtension<AppInputTheme> lerp(<br/> covariant ThemeExtension<AppInputTheme>? other,<br/> double t,<br/> ) {<br/> if (other is! AppInputTheme) {<br/> return this;<br/> }<br/><br/> return AppInputTheme(<br/> defaultText: Color.lerp(defaultText, other.defaultText, t)!,<br/> focusedOnBrand: Color.lerp(focusedOnBrand, other.focusedOnBrand, t)!,<br/> focusedTextDefault: Color.lerp(<br/> focusedTextDefault,<br/> other.focusedTextDefault,<br/> t,<br/> )!,<br/> errorTextDefault: Color.lerp(<br/> errorTextDefault,<br/> other.errorTextDefault,<br/> t,<br/> )!,<br/> successTextDefault: Color.lerp(<br/> successTextDefault,<br/> other.successTextDefault,<br/> t,<br/> )!,<br/> disabledText: Color.lerp(disabledText, other.disabledText, t)!,<br/> borderDefault: Color.lerp(borderDefault, other.borderDefault, t)!,<br/> borderHover: Color.lerp(borderHover, other.borderHover, t)!,<br/> borderFocused: Color.lerp(borderFocused, other.borderFocused, t)!,<br/> borderError: Color.lerp(borderError, other.borderError, t)!,<br/> borderSuccess: Color.lerp(borderSuccess, other.borderSuccess, t)!,<br/> borderDisabled: Color.lerp(borderDisabled, other.borderDisabled, t)!,<br/> defaultColor: Color.lerp(defaultColor, other.defaultColor, t)!,<br/> disabledColor: Color.lerp(disabledColor, other.disabledColor, t)!,<br/> );<br/> }<br/>}</span></pre><p id="043c" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Moreover, we have an <strong class="nw qh">AppTypography</strong> class, which collects font sizes as an independent theme.</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="fa49" class="px mw mx pu b bg py pz l qa qb">/// {@template app_typography}<br/>/// Theme class which provides configuration of [TextStyle]<br/>/// {@endtemplate}<br/>interface class AppTypography extends ThemeExtension<AppTypography> {<br/> /// {@macro app_typography}<br/> AppTypography({<br/> required this.buttonLarge,<br/> required this.buttonMedium,<br/> required this.buttonSmall,<br/> });<br/><br/> /// Button Large<br/> final TextStyle buttonLarge;<br/><br/> /// Button Medium<br/> final TextStyle buttonMedium;<br/><br/> /// Button Small<br/> final TextStyle buttonSmall;<br/><br/> @override<br/> ThemeExtension<AppTypography> copyWith({<br/> TextStyle? buttonLarge,<br/> TextStyle? buttonMedium,<br/> TextStyle? buttonSmall,<br/> }) {<br/> return AppTypography(<br/> buttonLarge: buttonLarge ?? this.buttonLarge,<br/> buttonMedium: buttonMedium ?? this.buttonMedium,<br/> buttonSmall: buttonSmall ?? this.buttonSmall,<br/> );<br/> }<br/><br/> @override<br/> ThemeExtension<AppTypography> lerp(<br/> covariant ThemeExtension<AppTypography>? other,<br/> double t,<br/> ) {<br/> if (other is! AppTypography) {<br/> return this;<br/> }<br/><br/> return AppTypography(<br/> buttonLarge: TextStyle.lerp(buttonLarge, other.buttonLarge, t)!,<br/> buttonMedium: TextStyle.lerp(buttonMedium, other.buttonMedium, t)!,<br/> buttonSmall: TextStyle.lerp(buttonSmall, other.buttonSmall, t)!,<br/> );<br/> }<br/>}<br/><br/>/// {@macro app_typography}<br/>class AppRegularTypography extends AppTypography {<br/> /// {@macro app_typography}<br/> AppRegularTypography({<br/> super.buttonLarge = const TextStyle(<br/> fontSize: 16,<br/> height: 24 / 16,<br/> fontWeight: FontWeight.w500,<br/> ),<br/> super.buttonMedium = const TextStyle(<br/> fontSize: 14,<br/> height: 20 / 14,<br/> fontWeight: FontWeight.w500,<br/> ),<br/> super.buttonSmall = const TextStyle(<br/> fontSize: 14,<br/> height: 20 / 14,<br/> fontWeight: FontWeight.w500,<br/> ),<br/> ),<br/> });<br/>}</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="a503" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">After all… Components</h2><p id="4adb" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">Component development depends on the design given by the designer. In reality, all the things we have developed above depend on. In our case, for instance, text buttons have many versions, such as size, decoration, etc.</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma qj"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*K-wVpB_DLUMduAkignKPCg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*K-wVpB_DLUMduAkignKPCg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*K-wVpB_DLUMduAkignKPCg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*K-wVpB_DLUMduAkignKPCg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*K-wVpB_DLUMduAkignKPCg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*K-wVpB_DLUMduAkignKPCg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*K-wVpB_DLUMduAkignKPCg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*K-wVpB_DLUMduAkignKPCg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="697" loading="eager" role="presentation"/></picture></div></div><figcaption class="qk ff ql lz ma qm qn bf b bg z du">We had many buttons, but I have shared some of them (text buttons)</figcaption></figure><p id="f33c" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Therefore, we have created a base class for our text buttons. Here is the code snippet:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="1349" class="px mw mx pu b bg py pz l qa qb">/// {@template app_text_button}<br/>/// A custom text button widget that adapts to the platform.<br/>/// {@endtemplate}<br/>abstract class AppTextButton extends StatelessWidget {<br/> /// {@macro app_text_button}<br/> const AppTextButton({<br/> super.key,<br/> required this.label,<br/> this.onTap,<br/> this.leading,<br/> this.trailing,<br/> this.appButtonSize = AppButtonSize.medium,<br/> });<br/><br/> /// The label for the text button.<br/> final String label;<br/><br/> /// The callback function for the text button.<br/> final VoidCallback? onTap;<br/><br/> /// The leading icon for the text button.<br/> final IconBuilder? leading;<br/><br/> /// The trailing icon for the text button.<br/> final IconBuilder? trailing;<br/><br/> /// The size of the text button.<br/> final AppButtonSize appButtonSize;<br/><br/> /// The background color for the text button.<br/> Color backgroundColor(BuildContext context);<br/><br/> /// The focus color for the text button.<br/> Color focusColor(BuildContext context);<br/><br/> /// The hover color for the text button.<br/> Color hoverColor(BuildContext context);<br/><br/> /// The disabled color for the text button.<br/> Color disabledColor(BuildContext context);<br/><br/> /// The text color for the text button.<br/> Color textColor(BuildContext context);<br/><br/> /// The disabled text color for the text button.<br/> Color disabledTextColor(BuildContext context) {<br/> return context.buttonTheme.primaryTextDisabled;<br/> }<br/><br/> /// The default border for the text button.<br/> BorderSide defaultBorder(BuildContext context) => BorderSide.none;<br/><br/> /// The focused border for the text button.<br/> BorderSide focusedBorder(BuildContext context) => BorderSide.none;<br/><br/> /// The hover border for the text button.<br/> BorderSide hoverBorder(BuildContext context) => BorderSide.none;<br/><br/> /// The disabled border for the text button.<br/> BorderSide disabledBorder(BuildContext context) => BorderSide.none;<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> final betweenSpace = switch (appButtonSize) {<br/> AppButtonSize.small ||<br/> AppButtonSize.xSmall ||<br/> AppButtonSize.medium =><br/> AppSpacing.xs,<br/> AppButtonSize.large || AppButtonSize.xlarge => AppSpacing.sm,<br/> AppButtonSize.xxLarge => AppSpacing.lg,<br/> };<br/><br/> final inputTextColor = WidgetStateProperty.resolveWith(<br/> (states) {<br/> if (states.contains(WidgetState.disabled)) {<br/> return disabledTextColor(context);<br/> }<br/><br/> return textColor(context);<br/> },<br/> );<br/><br/> return ElevatedButton(<br/> style: ButtonStyle(<br/> elevation: WidgetStateProperty.all(0),<br/> splashFactory: NoSplash.splashFactory,<br/> overlayColor: WidgetStateProperty.resolveWith(<br/> (states) {<br/> if (states.contains(WidgetState.disabled)) {<br/> return disabledColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.hovered)) {<br/> return hoverColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.focused)) {<br/> return focusColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.pressed)) {<br/> return focusColor(context);<br/> }<br/><br/> return backgroundColor(context);<br/> },<br/> ),<br/> shape: WidgetStateProperty.resolveWith(<br/> (states) {<br/> const shape = RoundedRectangleBorder(<br/> borderRadius: BorderRadius.all(AppRadius.md),<br/> );<br/><br/> if (states.contains(WidgetState.disabled)) {<br/> return shape.copyWith(side: disabledBorder(context));<br/> }<br/><br/> if (states.contains(WidgetState.focused)) {<br/> return shape.copyWith(side: focusedBorder(context));<br/> }<br/><br/> if (states.contains(WidgetState.hovered)) {<br/> return shape.copyWith(side: hoverBorder(context));<br/> }<br/><br/> if (states.contains(WidgetState.pressed)) {<br/> return shape.copyWith(side: focusedBorder(context));<br/> }<br/><br/> return shape.copyWith(side: defaultBorder(context));<br/> },<br/> ),<br/> backgroundColor: WidgetStateProperty.resolveWith(<br/> (states) {<br/> if (states.contains(WidgetState.disabled)) {<br/> return disabledColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.hovered)) {<br/> return hoverColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.focused)) {<br/> return focusColor(context);<br/> }<br/><br/> if (states.contains(WidgetState.pressed)) {<br/> return focusColor(context);<br/> }<br/><br/> return backgroundColor(context);<br/> },<br/> ),<br/> foregroundColor: inputTextColor,<br/> fixedSize: WidgetStateProperty.all(<br/> switch (appButtonSize) {<br/> AppButtonSize.small ||<br/> AppButtonSize.xSmall =><br/> const Size(double.infinity, 36),<br/> AppButtonSize.medium => const Size(double.infinity, 40),<br/> AppButtonSize.large => const Size(double.infinity, 44),<br/> AppButtonSize.xlarge => const Size(double.infinity, 48),<br/> AppButtonSize.xxLarge => const Size(double.infinity, 56),<br/> },<br/> ),<br/> padding: WidgetStateProperty.all(<br/> switch (appButtonSize) {<br/> AppButtonSize.small ||<br/> AppButtonSize.xSmall =><br/> const EdgeInsets.symmetric(horizontal: 12),<br/> AppButtonSize.medium => const EdgeInsets.symmetric(horizontal: 16),<br/> AppButtonSize.large => const EdgeInsets.symmetric(horizontal: 16),<br/> AppButtonSize.xlarge => const EdgeInsets.symmetric(horizontal: 20),<br/> AppButtonSize.xxLarge => const EdgeInsets.symmetric(horizontal: 24),<br/> },<br/> ),<br/> ),<br/> onPressed: onTap,<br/> child: Row(<br/> mainAxisAlignment: MainAxisAlignment.center,<br/> mainAxisSize: MainAxisSize.min,<br/> children: [<br/> if (leading != null) ...[<br/> leading!(<br/> onTap != null ? textColor(context) : disabledTextColor(context),<br/> ),<br/> SizedBox(width: betweenSpace),<br/> ],<br/> Padding(<br/> padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxs),<br/> child: Text(<br/> label,<br/> style: switch (appButtonSize) {<br/> AppButtonSize.small ||<br/> AppButtonSize.xSmall =><br/> context.typography.buttonSmall,<br/> AppButtonSize.medium => context.typography.buttonMedium,<br/> AppButtonSize.large => context.typography.buttonLarge,<br/> AppButtonSize.xlarge => context.typography.buttonXLarge,<br/> AppButtonSize.xxLarge => context.typography.button2XLarge,<br/> },<br/> ),<br/> ),<br/> if (trailing != null) ...[<br/> SizedBox(width: betweenSpace),<br/> trailing!(<br/> onTap != null ? textColor(context) : disabledTextColor(context),<br/> ),<br/> ],<br/> ],<br/> ),<br/> );<br/> }<br/>}</span></pre><p id="2011" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk"><strong class="nw qh">AppButtonSize</strong> is a simple enum provided by us:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="1dee" class="px mw mx pu b bg py pz l qa qb">/// Enum for button sizes<br/>enum AppButtonSize {<br/> /// Extra small button size<br/> xSmall,<br/><br/> /// Small button size<br/> small,<br/><br/> /// Medium button size<br/> medium,<br/><br/> /// Large button size<br/> large,<br/><br/> /// Extra large button size<br/> xlarge,<br/><br/> /// Extra extra large button size<br/> xxLarge,<br/>}</span></pre><p id="f082" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk"><strong class="nw qh">IconBuilder </strong>is a simple typedef provided by us. In some cases, we can need to change the color of an icon within the inner state of the button (focus, hover, and other state colors can be applied to the color of the icon)</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="6c4e" class="px mw mx pu b bg py pz l qa qb">/// A function that builds an icon widget.<br/>typedef IconBuilder = Widget Function(Color iconColor);</span></pre><p id="6357" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Eventually, with the help of the base <strong class="nw qh">AppTextButton</strong> class, we can create our child classes. So, our Primary, Secondary, and Outlined text buttons’ codes will be like the following:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="d001" class="px mw mx pu b bg py pz l qa qb">/// {@template primary_text_button}<br/>/// A custom primary text button widget that adapts to the platform.<br/>/// {@endtemplate}<br/>class PrimaryTextButton extends AppTextButton {<br/> /// {@macro primary_text_button}<br/> const PrimaryTextButton({<br/> super.key,<br/> required super.label,<br/> super.onTap,<br/> super.leading,<br/> super.trailing,<br/> super.appButtonSize,<br/> });<br/><br/> @override<br/> Color backgroundColor(BuildContext context) {<br/> return context.buttonTheme.primaryDefault;<br/> }<br/><br/> @override<br/> Color disabledColor(BuildContext context) {<br/> return context.buttonTheme.primaryDisabled;<br/> }<br/><br/> @override<br/> Color focusColor(BuildContext context) {<br/> return context.buttonTheme.primaryFocused;<br/> }<br/><br/> @override<br/> Color hoverColor(BuildContext context) {<br/> return context.buttonTheme.primaryHover;<br/> }<br/><br/> @override<br/> Color textColor(BuildContext context) {<br/> return context.buttonTheme.primaryText;<br/> }<br/>}</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="6389" class="px mw mx pu b bg py pz l qa qb">/// {@template secondary_text_button}<br/>/// A custom secondary text button widget that adapts to the platform.<br/>/// {@endtemplate}<br/>///<br/>class SecondaryTextButton extends AppTextButton {<br/> /// {@macro secondary_text_button}<br/> const SecondaryTextButton({<br/> super.key,<br/> required super.label,<br/> super.onTap,<br/> super.leading,<br/> super.trailing,<br/> super.appButtonSize,<br/> });<br/><br/> @override<br/> Color backgroundColor(BuildContext context) {<br/> return context.buttonTheme.secondaryDefault;<br/> }<br/><br/> @override<br/> Color disabledColor(BuildContext context) {<br/> return context.buttonTheme.secondaryDisabled;<br/> }<br/><br/> @override<br/> Color focusColor(BuildContext context) {<br/> return context.buttonTheme.secondaryFocused;<br/> }<br/><br/> @override<br/> Color hoverColor(BuildContext context) {<br/> return context.buttonTheme.secondaryHover;<br/> }<br/><br/> @override<br/> Color textColor(BuildContext context) {<br/> return context.buttonTheme.primaryTextOnBrand;<br/> }<br/>}</span></pre><pre class="qc pt pu pv bp pw bb bk"><span id="0ab3" class="px mw mx pu b bg py pz l qa qb">/// {@template outline_text_button}<br/>/// A custom outline text button widget that adapts to the platform.<br/>/// {@endtemplate}<br/>class OutlineTextButton extends AppTextButton {<br/> /// {@macro outline_text_button}<br/> const OutlineTextButton({<br/> super.key,<br/> required super.label,<br/> super.onTap,<br/> super.leading,<br/> super.trailing,<br/> super.appButtonSize,<br/> });<br/><br/> @override<br/> Color backgroundColor(BuildContext context) {<br/> return context.buttonTheme.outlinedDefault;<br/> }<br/><br/> @override<br/> Color disabledColor(BuildContext context) {<br/> return context.buttonTheme.outlinedDisabled;<br/> }<br/><br/> @override<br/> Color focusColor(BuildContext context) {<br/> return context.buttonTheme.outlinedFocused;<br/> }<br/><br/> @override<br/> Color hoverColor(BuildContext context) {<br/> return context.buttonTheme.outlinedHover;<br/> }<br/><br/> @override<br/> Color textColor(BuildContext context) {<br/> return context.buttonTheme.buttonLineDefault;<br/> }<br/><br/> @override<br/> BorderSide defaultBorder(BuildContext context) {<br/> return BorderSide(<br/> color: context.buttonTheme.buttonLineDefault,<br/> );<br/> }<br/><br/> @override<br/> BorderSide focusedBorder(BuildContext context) {<br/> return BorderSide(<br/> color: context.buttonTheme.buttonLineDefault,<br/> );<br/> }<br/><br/> @override<br/> BorderSide hoverBorder(BuildContext context) {<br/> return BorderSide(<br/> color: context.buttonTheme.buttonLineDefault,<br/> );<br/> }<br/><br/> @override<br/> BorderSide disabledBorder(BuildContext context) {<br/> return BorderSide(<br/> color: context.buttonTheme.outlinedBorderDisabled,<br/> );<br/> }<br/>}</span></pre><p id="189e" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">For the text field, we have created again an independent AppTextField class.</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma qo"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*7YRH1f5uCDNfVnlDO-QcJg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*7YRH1f5uCDNfVnlDO-QcJg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*7YRH1f5uCDNfVnlDO-QcJg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*7YRH1f5uCDNfVnlDO-QcJg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*7YRH1f5uCDNfVnlDO-QcJg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*7YRH1f5uCDNfVnlDO-QcJg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*7YRH1f5uCDNfVnlDO-QcJg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*7YRH1f5uCDNfVnlDO-QcJg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="518" loading="lazy" role="presentation"/></picture></div></div></figure><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="0772" class="px mw mx pu b bg py pz l qa qb">/// {@template app_text_field}<br/>/// A customizable text field widget with various customization options.<br/>/// {@endtemplate}<br/>class AppTextField extends StatelessWidget {<br/> /// {@macro app_text_field}<br/> const AppTextField({<br/> super.key,<br/> this.controller,<br/> this.labelText,<br/> this.enabled = true,<br/> this.obscureText = false,<br/> this.onChanged,<br/> this.autovalidateMode = AutovalidateMode.onUserInteraction,<br/> this.validator,<br/> this.helperText,<br/> this.errorText,<br/> this.suffixIcon,<br/> this.suffixIconConstraints =<br/> const BoxConstraints(minHeight: 24, minWidth: 40),<br/> this.prefixIcon,<br/> this.prefixIconConstraints =<br/> const BoxConstraints(minHeight: 24, minWidth: 40),<br/> this.autofillHints,<br/> this.onEditingComplete,<br/> this.inputFormatters,<br/> this.keyboardType,<br/> this.maxLines = 1,<br/> });<br/><br/> /// The controller for the text field.<br/> final TextEditingController? controller;<br/><br/> /// The label text for the text field.<br/> final String? labelText;<br/><br/> /// Whether the text field is enabled.<br/> final bool enabled;<br/><br/> /// Whether the text field is obscured.<br/> final bool obscureText;<br/><br/> /// Called when the text field value changes.<br/> final ValueChanged<String>? onChanged;<br/><br/> /// The autovalidate mode for the text field.<br/> final AutovalidateMode autovalidateMode;<br/><br/> /// The validator for the text field.<br/> final FormFieldValidator<String>? validator;<br/><br/> /// The helper text for the text field.<br/> final String? helperText;<br/><br/> /// The error text for the text field.<br/> final String? errorText;<br/><br/> /// The suffix icon for the text field.<br/> final Widget? suffixIcon;<br/><br/> /// The constraints for the suffix icon.<br/> final BoxConstraints? suffixIconConstraints;<br/><br/> /// The prefix icon for the text field.<br/> final Widget? prefixIcon;<br/><br/> /// The constraints for the prefix icon.<br/> final BoxConstraints? prefixIconConstraints;<br/><br/> /// The autofillhints for app text field.<br/> final Iterable<String>? autofillHints;<br/><br/> /// Called when the text field value completed.<br/> final VoidCallback? onEditingComplete;<br/><br/> /// The input formatters for the text field.<br/> final List<TextInputFormatter>? inputFormatters;<br/><br/> /// The keyboard type for the text field.<br/> final TextInputType? keyboardType;<br/><br/> /// the maximum lines available in text field.<br/> final int maxLines;<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> return TextFormField(<br/> keyboardType: keyboardType,<br/> inputFormatters: inputFormatters,<br/> onEditingComplete: onEditingComplete,<br/> autofillHints: autofillHints,<br/> controller: controller,<br/> enabled: enabled,<br/> obscureText: obscureText,<br/> onChanged: onChanged,<br/> autovalidateMode: autovalidateMode,<br/> validator: validator,<br/> maxLines: maxLines,<br/> style: WidgetStateTextStyle.resolveWith(<br/> (states) {<br/> late final Color textColor;<br/><br/> if (states.contains(WidgetState.error)) {<br/> textColor = context.inputTheme.focusedTextDefault;<br/> } else if (states.contains(WidgetState.focused)) {<br/> textColor = context.inputTheme.focusedTextDefault;<br/> } else if (states.contains(WidgetState.disabled)) {<br/> textColor = context.inputTheme.disabledText;<br/> } else {<br/> textColor = context.inputTheme.defaultText;<br/> }<br/><br/> return context.typography.inputPlaceHolder.copyWith(<br/> color: textColor,<br/> );<br/> },<br/> ),<br/> cursorColor: context.inputTheme.focusedTextDefault,<br/> cursorHeight: 16,<br/> decoration: InputDecoration(<br/> labelText: labelText,<br/> labelStyle: WidgetStateTextStyle.resolveWith(<br/> (states) {<br/> late final Color textColor;<br/><br/> if (states.contains(WidgetState.error)) {<br/> textColor = context.inputTheme.errorTextDefault;<br/> } else if (states.contains(WidgetState.focused)) {<br/> textColor = context.inputTheme.focusedOnBrand;<br/> } else if (states.contains(WidgetState.disabled)) {<br/> textColor = context.inputTheme.disabledText;<br/> } else {<br/> textColor = context.inputTheme.defaultText;<br/> }<br/><br/> return context.typography.inputPlaceHolder.copyWith(<br/> color: textColor,<br/> );<br/> },<br/> ),<br/> floatingLabelStyle: WidgetStateTextStyle.resolveWith(<br/> (states) {<br/> late final Color textColor;<br/><br/> if (states.contains(WidgetState.error)) {<br/> textColor = context.inputTheme.errorTextDefault;<br/> } else if (states.contains(WidgetState.focused)) {<br/> textColor = context.inputTheme.focusedOnBrand;<br/> } else {<br/> textColor = context.inputTheme.defaultText;<br/> }<br/><br/> return context.typography.inputLabel.copyWith(<br/> color: textColor,<br/> );<br/> },<br/> ),<br/> filled: true,<br/> fillColor: enabled<br/> ? context.inputTheme.defaultColor<br/> : context.inputTheme.disabledColor,<br/> border: MaterialStateOutlineInputBorder.resolveWith(<br/> (states) {<br/> late final Color borderColor;<br/><br/> if (states.contains(WidgetState.error)) {<br/> borderColor = context.inputTheme.borderError;<br/> } else if (states.contains(WidgetState.focused)) {<br/> borderColor = context.inputTheme.borderFocused;<br/> } else if (states.contains(WidgetState.disabled)) {<br/> borderColor = context.inputTheme.borderDisabled;<br/> } else if (states.contains(WidgetState.hovered)) {<br/> borderColor = context.inputTheme.borderHover;<br/> } else {<br/> borderColor = context.inputTheme.borderDefault;<br/> }<br/><br/> return OutlineInputBorder(<br/> borderRadius: const BorderRadius.all(AppRadius.md),<br/> borderSide: BorderSide(<br/> color: borderColor,<br/> ),<br/> );<br/> },<br/> ),<br/> hoverColor: Colors.transparent,<br/> focusColor: Colors.transparent,<br/> helperText: helperText,<br/> helperStyle: WidgetStateTextStyle.resolveWith(<br/> (states) {<br/> late final Color textColor;<br/><br/> if (states.contains(WidgetState.error)) {<br/> textColor = context.inputTheme.errorTextDefault;<br/> } else if (states.contains(WidgetState.focused)) {<br/> textColor = context.inputTheme.focusedOnBrand;<br/> } else if (states.contains(WidgetState.disabled)) {<br/> textColor = context.inputTheme.disabledText;<br/> } else {<br/> textColor = context.inputTheme.defaultText;<br/> }<br/><br/> return context.typography.inputHint.copyWith(<br/> color: textColor,<br/> );<br/> },<br/> ),<br/> errorText: errorText,<br/> errorStyle: context.typography.inputHint.copyWith(<br/> color: context.inputTheme.errorTextDefault,<br/> ),<br/> suffixIcon: suffixIcon,<br/> prefixIcon: prefixIcon,<br/> suffixIconConstraints: suffixIconConstraints,<br/> prefixIconConstraints: prefixIconConstraints,<br/> ),<br/> );<br/> }<br/>}</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="c8c5" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Extension to get themes with context</h2><p id="c58e" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">To get themes with context, we have created a simple extension class:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="9fc2" class="px mw mx pu b bg py pz l qa qb">/// An extension on [BuildContext] that provides access to the current theme.<br/>extension ThemeExt on BuildContext {<br/> /// The current theme.<br/> ThemeData get theme => Theme.of(this);<br/><br/> ///the current button theme<br/> AppButtonTheme get buttonTheme =><br/> theme.extension<AppTheme>()!.appButtonTheme as AppButtonTheme;<br/><br/> /// The current app checkboxTheme.<br/> AppCheckboxTheme get checkboxTheme =><br/> theme.extension<AppTheme>()!.appCheckboxTheme as AppCheckboxTheme;<br/><br/> /// The current app iconTheme.<br/> AppIconTheme get iconTheme =><br/> theme.extension<AppTheme>()!.appIconTheme as AppIconTheme;<br/><br/> /// The current app inputTheme.<br/> AppInputTheme get inputTheme =><br/> theme.extension<AppTheme>()!.appInputTheme as AppInputTheme;<br/><br/> /// The current app radioTheme.<br/> AppRadioTheme get radioTheme =><br/> theme.extension<AppTheme>()!.appRadioTheme as AppRadioTheme;<br/><br/> /// The current app toggleTheme.<br/> AppToggleTheme get toggleTheme =><br/> theme.extension<AppTheme>()!.appToggleTheme as AppToggleTheme;<br/><br/> /// The current app typographyTheme.<br/> AppTypographyTheme get typographyTheme =><br/> theme.extension<AppTheme>()!.appTypographyTheme as AppTypographyTheme;<br/><br/> /// The current app avatarTheme.<br/> AppAvatarTheme get avatarTheme =><br/> theme.extension<AppTheme>()!.appAvatarTheme as AppAvatarTheme;<br/><br/> /// The current app typography.<br/> AppTypography get typography =><br/> theme.extension<AppTheme>()!.appTypography as AppTypography;<br/><br/> /// The current app navigationTheme.<br/> AppNavigationTheme get navigationTheme =><br/> theme.extension<AppTheme>()!.appNavigationTheme as AppNavigationTheme;<br/><br/> /// The current app layoutTheme.<br/> AppLayoutTheme get layoutTheme =><br/> theme.extension<AppTheme>()!.appLayoutTheme as AppLayoutTheme;<br/><br/> /// The current app badgeTheme.<br/> AppBadgeTheme get badgeTheme =><br/> theme.extension<AppTheme>()!.appBadgeTheme as AppBadgeTheme;<br/><br/> /// The current app breadcrumbTheme.<br/> AppBreadCrumbTheme get appBreadCrumbTheme =><br/> theme.extension<AppTheme>()!.appBreadCrumbTheme as AppBreadCrumbTheme;<br/><br/> /// The current app appDropdownTheme.<br/> AppDropdownTheme get appDropdownTheme =><br/> theme.extension<AppTheme>()!.appDropdownTheme as AppDropdownTheme;<br/>}</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="2823" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">So… What is AppTheme there?</h2><p id="3c24" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">AppTheme is a simple, immutable class (<strong class="nw qh">ThemeExtension</strong>) that provides all themes to our application.</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="200b" class="px mw mx pu b bg py pz l qa qb">/// {@template app_theme}<br/>/// Configuration class which collects all Themes of app together and provides<br/>/// them as a single instance<br/>/// {@endtemplate}<br/>class AppTheme extends ThemeExtension<AppTheme> {<br/> /// {@macro app_theme}<br/> const AppTheme({<br/> required this.appButtonTheme,<br/> required this.appInputTheme,<br/> });<br/><br/> /// {@macro app_theme}<br/> factory AppTheme.light() {<br/> return AppTheme(<br/> appButtonTheme: AppButtonTheme.light(),<br/> appInputTheme: AppInputTheme.light(),<br/> );<br/> }<br/><br/> /// [AppButtonTheme] instance provides configuration of buttons<br/> final ThemeExtension<AppButtonTheme> appButtonTheme;<br/><br/> /// [AppInputTheme] instance provides configuration of [AppTextField]<br/> final ThemeExtension<AppInputTheme> appInputTheme;<br/><br/><br/> @override<br/> ThemeExtension<AppTheme> copyWith({<br/> ThemeExtension<AppButtonTheme>? appButtonTheme,<br/> ThemeExtension<AppInputTheme>? appInputTheme,<br/> }) {<br/> return AppTheme(<br/> appButtonTheme: appButtonTheme ?? this.appButtonTheme,<br/> appInputTheme: appInputTheme ?? this.appInputTheme,<br/> );<br/> }<br/><br/> @override<br/> ThemeExtension<AppTheme> lerp(<br/> covariant ThemeExtension<AppTheme>? other,<br/> double t,<br/> ) {<br/> if (other is! AppTheme) {<br/> return this;<br/> }<br/><br/> return AppTheme(<br/> appButtonTheme: appButtonTheme.lerp(other.appButtonTheme, t),<br/> appInputTheme: appInputTheme.lerp(other.appInputTheme, t),<br/> );<br/> }<br/>}</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="cb4a" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">How will we provide our theme for a new project?</h2><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div class="lz ma qp"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*fWTkV2UN5fDvvjRm.gif 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*fWTkV2UN5fDvvjRm.gif 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*fWTkV2UN5fDvvjRm.gif 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*fWTkV2UN5fDvvjRm.gif 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*fWTkV2UN5fDvvjRm.gif 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*fWTkV2UN5fDvvjRm.gif 1100w, https://miro.medium.com/v2/resize:fit:960/format:webp/0*fWTkV2UN5fDvvjRm.gif 960w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 480px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*fWTkV2UN5fDvvjRm.gif 640w, https://miro.medium.com/v2/resize:fit:720/0*fWTkV2UN5fDvvjRm.gif 720w, https://miro.medium.com/v2/resize:fit:750/0*fWTkV2UN5fDvvjRm.gif 750w, https://miro.medium.com/v2/resize:fit:786/0*fWTkV2UN5fDvvjRm.gif 786w, https://miro.medium.com/v2/resize:fit:828/0*fWTkV2UN5fDvvjRm.gif 828w, https://miro.medium.com/v2/resize:fit:1100/0*fWTkV2UN5fDvvjRm.gif 1100w, https://miro.medium.com/v2/resize:fit:960/0*fWTkV2UN5fDvvjRm.gif 960w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 480px"/><img alt="" class="bh lb mm c" width="480" height="268" loading="eager" role="presentation"/></picture></div></figure><p id="503d" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">It is not a difficult process. If you know about <strong class="nw qh">InheritedWidget</strong>, you can understand this step easily.</p><p id="e195" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Therefore, we created a simple <strong class="nw qh">InheritedWidget</strong> called <strong class="nw qh">ThemeScope</strong> to provide our design system for a new project.</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="1225" class="px mw mx pu b bg py pz l qa qb">/// {@template theme_scope}<br/>/// InheritedWidget provides [AppTheme] for app<br/>/// {@endtemplate}<br/>class ThemeScope extends InheritedWidget {<br/> /// {@macro theme_scope}<br/> const ThemeScope({<br/> super.key,<br/> required Widget child,<br/> required this.themeMode,<br/> required this.appTheme,<br/> }) : super(child: child);<br/><br/> /// The current theme mode.<br/> final ThemeMode themeMode;<br/><br/> /// The current app theme.<br/> final AppTheme appTheme;<br/><br/> /// The current theme.<br/> static ThemeScope of(BuildContext context) {<br/> final result = context.dependOnInheritedWidgetOfExactType<ThemeScope>();<br/> assert(result != null, 'No ThemeScope found in context');<br/> return result!;<br/> }<br/><br/> @override<br/> bool updateShouldNotify(ThemeScope oldWidget) => true;<br/>}</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="7b47" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">ThemeMode handler</h2><p id="89e3" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">For handling the dark, light, and system mode switching process, your can write a simple controller and initializer as the following:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="7f8b" class="px mw mx pu b bg py pz l qa qb">const _kThemeMode = 'themeMode';<br/><br/>/// {@template theme_scope_widget}<br/>/// A class which handles all theme processes<br/>///<br/>/// initialize() method should be used as app starter in order to use<br/>/// [AppTheme] in the app<br/><br/>/// {@endtemplate}<br/>class ThemeScopeWidget extends StatefulWidget {<br/> /// {@macro theme_scope_widget}<br/> const ThemeScopeWidget({<br/> super.key,<br/> required this.child,<br/> required this.preferences,<br/> });<br/><br/> /// The child widget<br/> final Widget child;<br/><br/> /// The shared preferences<br/> final SharedPreferences preferences;<br/><br/> /// Initialize the [ThemeScopeWidget] with the given [child] widget<br/> static Future<ThemeScopeWidget> initialize(Widget child) async {<br/> final preferences = await SharedPreferences.getInstance();<br/> return ThemeScopeWidget(<br/> preferences: preferences,<br/> child: child,<br/> );<br/> }<br/><br/> /// In order to use methods of [ThemeScopeWidget] this function<br/> /// should be called first. Theme change process will handled by<br/> /// [ThemeScopeWidget] automatically.<br/> static ThemeScopeWidgetState? of(BuildContext context) {<br/> return context.findRootAncestorStateOfType<ThemeScopeWidgetState>();<br/> }<br/><br/> @override<br/> State<ThemeScopeWidget> createState() => ThemeScopeWidgetState();<br/>}<br/><br/>/// The state for [ThemeScopeWidget].<br/>class ThemeScopeWidgetState extends State<ThemeScopeWidget> {<br/> ThemeMode? _themeMode;<br/><br/> /// Change the theme mode<br/> Future<void> changeTo(ThemeMode themeMode) async {<br/> if (_themeMode == themeMode) return;<br/><br/> try {<br/> final index = ThemeMode.values.indexOf(themeMode);<br/> await widget.preferences.setInt(_kThemeMode, index);<br/><br/> setState(() {<br/> _themeMode = themeMode;<br/> });<br/> } on Exception catch (_) {}<br/> }<br/><br/> @override<br/> void didChangeDependencies() {<br/> super.didChangeDependencies();<br/><br/> try {<br/> final themeModeIndex = widget.preferences.getInt(_kThemeMode) ?? 0;<br/> final themeMode = ThemeMode.values[themeModeIndex];<br/><br/> _themeMode = themeMode;<br/> } on Exception catch (_) {<br/> _themeMode = ThemeMode.system;<br/> }<br/> }<br/><br/> @override<br/> Widget build(BuildContext context) {<br/> final brightness = MediaQuery.platformBrightnessOf(context);<br/><br/> final appTheme = switch (_themeMode!) {<br/> ThemeMode.light => AppTheme.light(),<br/> ThemeMode.dark => AppTheme.light(),<br/> ThemeMode.system =><br/> brightness == Brightness.dark ? AppTheme.light() : AppTheme.light(),<br/> };<br/><br/> return ThemeScope(<br/> themeMode: _themeMode!,<br/> appTheme: appTheme,<br/> child: widget.child,<br/> );<br/> }<br/>}</span></pre><blockquote class="qd qe qf"><p id="70d1" class="nu nv qg nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">You can call <strong class="nw qh">ThemeScopeWidget.of(context).changeTo</strong> function to change your ThemeMode in anywhere!</p></blockquote></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="aa4f" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">The last step is to initialize your project with our Design System</h2><p id="8673" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">We wrote this design system implementation as an independent package and used it in different apps.</p><p id="41f5" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Firstly, we should initialize our wrapper:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="90a0" class="px mw mx pu b bg py pz l qa qb">void main() async {<br/> WidgetsFlutterBinding.ensureInitialized();<br/> final app = await ThemeScopeWidget.initialize(const MyApp());<br/> runApp(app);<br/> }</span></pre><p id="e8fa" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">We have another option to provide:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="2c7f" class="px mw mx pu b bg py pz l qa qb">void main() async {<br/> WidgetsFlutterBinding.ensureInitialized();<br/> final preferences = await SharedPreferences.getInstance();<br/><br/> runApp(<br/> ThemeScopeWidget(<br/> preferences: preferences,<br/> child: const MyApp(),<br/> ),<br/> );<br/> }</span></pre><p id="6502" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">And, in the MaterialApp, it should be registered as an extension:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="af6e" class="px mw mx pu b bg py pz l qa qb">final theme = ThemeScope.of(context);<br/><br/>return MaterialApp(<br/> title: 'Flutter App',<br/> themeMode: theme.themeMode,<br/> theme: ThemeData(extensions: [theme.appTheme]),<br/> darkTheme: ThemeData(extensions: [theme.appTheme]),<br/> home: const MyHomePage(title: 'Flutter Demo Home Page'),<br/>);</span></pre><p id="0ed6" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">There are a few extension methods that allow developers to access any theme, typography, and just a few lines of code.</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="95bf" class="px mw mx pu b bg py pz l qa qb">context.buttonTheme.linkHover;<br/>context.checkboxTheme.disabled;<br/>context.typography.titleSmall;</span></pre><p id="dceb" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">To change the theme of app, just following function should be called:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="2e75" class="px mw mx pu b bg py pz l qa qb">final themeScope = ThemeScopeWidget.of(context);<br/>themeScope.changeTo(ThemeMode.light);</span></pre></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="9398" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">What is about Assets?</h2><p id="77d9" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">In general, assets are the part of the design system. Therefore, we created an independent package that stores and emits PNGs, SVGs, etc, for any project as a package.</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma qq"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*PTRxNMJocD1modj5OucZzg.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*PTRxNMJocD1modj5OucZzg.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*PTRxNMJocD1modj5OucZzg.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*PTRxNMJocD1modj5OucZzg.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*PTRxNMJocD1modj5OucZzg.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*PTRxNMJocD1modj5OucZzg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*PTRxNMJocD1modj5OucZzg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*PTRxNMJocD1modj5OucZzg.png 640w, https://miro.medium.com/v2/resize:fit:720/1*PTRxNMJocD1modj5OucZzg.png 720w, https://miro.medium.com/v2/resize:fit:750/1*PTRxNMJocD1modj5OucZzg.png 750w, https://miro.medium.com/v2/resize:fit:786/1*PTRxNMJocD1modj5OucZzg.png 786w, https://miro.medium.com/v2/resize:fit:828/1*PTRxNMJocD1modj5OucZzg.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*PTRxNMJocD1modj5OucZzg.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*PTRxNMJocD1modj5OucZzg.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="618" loading="eager" role="presentation"/></picture></div></div></figure><p id="bc8f" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">So, as you can see, the <strong class="nw qh">assets</strong> package stores fonts, raster, and vector images.</p><ul class=""><li id="2083" class="nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or qr qs qt bk"><strong class="nw qh">rasters</strong> mean PNGs, JPEGs, etc</li><li id="9849" class="nu nv mx nw b nx qu nz oa ob qv od oe of qw oh oi oj qx ol om on qy op oq or qr qs qt bk"><strong class="nw qh">vectors</strong> mean SVGs</li></ul><p id="2043" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk"><strong class="nw qh"><em class="qg">Why did we divide them in the different package?</em></strong></p><p id="ae7b" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">To optimize SVGs, we have used the new way, for all SVGs in the vectors package will be compiled to optimized version at the build time:</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div role="button" tabindex="0" class="mi mj fj mk bh ml"><div class="lz ma qz"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/format:webp/1*vtmDBwLpn8o8WinXnn6UZA.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/1*vtmDBwLpn8o8WinXnn6UZA.png 640w, https://miro.medium.com/v2/resize:fit:720/1*vtmDBwLpn8o8WinXnn6UZA.png 720w, https://miro.medium.com/v2/resize:fit:750/1*vtmDBwLpn8o8WinXnn6UZA.png 750w, https://miro.medium.com/v2/resize:fit:786/1*vtmDBwLpn8o8WinXnn6UZA.png 786w, https://miro.medium.com/v2/resize:fit:828/1*vtmDBwLpn8o8WinXnn6UZA.png 828w, https://miro.medium.com/v2/resize:fit:1100/1*vtmDBwLpn8o8WinXnn6UZA.png 1100w, https://miro.medium.com/v2/resize:fit:1400/1*vtmDBwLpn8o8WinXnn6UZA.png 1400w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 700px"/><img alt="" class="bh lb mm c" width="700" height="243" loading="lazy" role="presentation"/></picture></div></div></figure><p id="6963" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">And, we need SSOT (Single Source of Truth) to get our assets:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="b8ff" class="px mw mx pu b bg py pz l qa qb">/// {@template app_icons}<br/>/// The [AppAssets] class contains all the icons used in the app.<br/>/// {@endtemplate}<br/>abstract class AppAssets {<br/> /// person icon<br/> static const person = AssetBytesLoader(<br/> 'vectors/person.svg',<br/> packageName: 'assets',<br/> );<br/><br/> /// left arrow icon<br/> static const leftArrow = AssetBytesLoader(<br/> 'vectors/left_icon.svg',<br/> packageName: 'assets',<br/> );<br/><br/> static const sittingSad = 'rasters/sitting_sad.png';<br/> static const magnifyingGlass = 'rasters/magnifying_glass.png';<br/>}</span></pre><blockquote class="qd qe qf"><p id="477e" class="nu nv qg nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">You can divide raster and vector graphics class too</p></blockquote><p id="7fb1" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">Keep in mind that, when you use raster graphic in any project as package (you defined <strong class="nw qh">asset</strong> package in pubspec and use it), you should define <strong class="nw qh">package</strong> field:</p><pre class="pi pj pk pl pm pt pu pv bp pw bb bk"><span id="e2b8" class="px mw mx pu b bg py pz l qa qb">Image.asset(AppAssets.sittingSad, package: 'assets')</span></pre><p id="5d92" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">And, if you want to export <strong class="nw qh">font</strong> from other package you should define your fonts inside <strong class="nw qh">lib</strong> folder. For more information, you can check the <a class="af ps" href="https://docs.flutter.dev/cookbook/design/package-fonts" rel="noopener ugc nofollow" target="_blank"><strong class="nw qh">Export fonts from a package</strong></a> article from Flutter documentation.</p></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><h2 id="a2a9" class="os mw mx bf my ot ou dy nc ov ow ea ng of ox oy oz oj pa pb pc on pd pe pf pg bk">Testing your design system as an independent app</h2><p id="e14c" class="pw-post-body-paragraph nu nv mx nw b nx ny nz oa ob oc od oe of og oh oi oj ok ol om on oo op oq or lu bk">For this purpose, we are using <a class="af ps" href="https://docs.widgetbook.io/" rel="noopener ugc nofollow" target="_blank"><strong class="nw qh">Widgetbook</strong></a>. It is a huge topic to write about. In order not to prolong the article further, I did not write about this topic in more detail. It has a great documentation, you can check it.</p></div></div></div><div class="ab cb mn mo mp mq" role="separator"><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt mu"></span><span class="mr by bm ms mt"></span></div><div class="lu lv lw lx ly"><div class="ab cb"><div class="ci bh fz ga gb gc"><p id="5c17" class="pw-post-body-paragraph nu nv mx nw b nx pn nz oa ob po od oe of pp oh oi oj pq ol om on pr op oq or lu bk">That is it. If you like my article, don’t forget to clap!</p><figure class="pi pj pk pl pm mh lz ma paragraph-image"><div class="lz ma ra"><picture><source srcSet="https://miro.medium.com/v2/resize:fit:640/format:webp/0*9A8DvdJQnbvdwQsB.gif 640w, https://miro.medium.com/v2/resize:fit:720/format:webp/0*9A8DvdJQnbvdwQsB.gif 720w, https://miro.medium.com/v2/resize:fit:750/format:webp/0*9A8DvdJQnbvdwQsB.gif 750w, https://miro.medium.com/v2/resize:fit:786/format:webp/0*9A8DvdJQnbvdwQsB.gif 786w, https://miro.medium.com/v2/resize:fit:828/format:webp/0*9A8DvdJQnbvdwQsB.gif 828w, https://miro.medium.com/v2/resize:fit:1100/format:webp/0*9A8DvdJQnbvdwQsB.gif 1100w, https://miro.medium.com/v2/resize:fit:1008/format:webp/0*9A8DvdJQnbvdwQsB.gif 1008w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 504px" type="image/webp"/><source data-testid="og" srcSet="https://miro.medium.com/v2/resize:fit:640/0*9A8DvdJQnbvdwQsB.gif 640w, https://miro.medium.com/v2/resize:fit:720/0*9A8DvdJQnbvdwQsB.gif 720w, https://miro.medium.com/v2/resize:fit:750/0*9A8DvdJQnbvdwQsB.gif 750w, https://miro.medium.com/v2/resize:fit:786/0*9A8DvdJQnbvdwQsB.gif 786w, https://miro.medium.com/v2/resize:fit:828/0*9A8DvdJQnbvdwQsB.gif 828w, https://miro.medium.com/v2/resize:fit:1100/0*9A8DvdJQnbvdwQsB.gif 1100w, https://miro.medium.com/v2/resize:fit:1008/0*9A8DvdJQnbvdwQsB.gif 1008w" sizes="(min-resolution: 4dppx) and (max-width: 700px) 50vw, (-webkit-min-device-pixel-ratio: 4) and (max-width: 700px) 50vw, (min-resolution: 3dppx) and (max-width: 700px) 67vw, (-webkit-min-device-pixel-ratio: 3) and (max-width: 700px) 65vw, (min-resolution: 2.5dppx) and (max-width: 700px) 80vw, (-webkit-min-device-pixel-ratio: 2.5) and (max-width: 700px) 80vw, (min-resolution: 2dppx) and (max-width: 700px) 100vw, (-webkit-min-device-pixel-ratio: 2) and (max-width: 700px) 100vw, 504px"/><img alt="" class="bh lb mm c" width="504" height="336" loading="eager" role="presentation"/></picture></div></figure></div></div></div></div></section></div></div></article></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="rb rc ab hx"><div class="rd ab"><a class="re ay am ao" rel="noopener follow" href="/tag/flutter?source=post_page-----bc2aebb8bb02--------------------------------"><div class="rf fj cx rg ge rh ri bf b bg z bk rj">Flutter</div></a></div><div class="rd ab"><a class="re ay am ao" rel="noopener follow" href="/tag/design-system?source=post_page-----bc2aebb8bb02--------------------------------"><div class="rf fj cx rg ge rh ri bf b bg z bk rj">Design System</div></a></div><div class="rd ab"><a class="re ay am ao" rel="noopener follow" href="/tag/inherited-widget?source=post_page-----bc2aebb8bb02--------------------------------"><div class="rf fj cx rg ge rh ri bf b bg z bk rj">Inherited Widget</div></a></div><div class="rd ab"><a class="re ay am ao" rel="noopener follow" href="/tag/assets-management?source=post_page-----bc2aebb8bb02--------------------------------"><div class="rf fj cx rg ge rh ri bf b bg z bk rj">Assets Management</div></a></div><div class="rd ab"><a class="re ay am ao" rel="noopener follow" href="/tag/components?source=post_page-----bc2aebb8bb02--------------------------------"><div class="rf fj cx rg ge rh ri bf b bg z bk rj">Components</div></a></div></div></div></div><div class="l"></div><footer class="rk mo rl rm rn ab q ro hh c"><div class="l ae"><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab cp rp"><div class="ab q js"><div class="rq l"><span class="l rr rs rt e d"><div class="ab q js jt"><div class="pw-multi-vote-icon fj ib ju jv jw"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter-community%2Fbc2aebb8bb02&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&user=Kanan+Yusubov&userId=571559932209&source=---footer_actions--bc2aebb8bb02---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="jx ao jy jz ka kb am kc kd ke jw"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l kf kg kh ki kj kk kl"><p class="bf b dv z du"><span class="km">--</span></p></div></div></span><span class="l h g f ru rv"><div class="ab q js jt"><div class="pw-multi-vote-icon fj ib ju jv jw"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerClapButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fvote%2Fflutter-community%2Fbc2aebb8bb02&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&user=Kanan+Yusubov&userId=571559932209&source=---footer_actions--bc2aebb8bb02---------------------clap_footer-----------"><div><div class="bm" aria-hidden="false"><div class="jx ao jy jz ka kb am kc kd ke jw"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-label="clap"><path fill-rule="evenodd" d="M11.37.828 12 3.282l.63-2.454zM13.916 3.953l1.523-2.112-1.184-.39zM8.589 1.84l1.522 2.112-.337-2.501zM18.523 18.92c-.86.86-1.75 1.246-2.62 1.33a6 6 0 0 0 .407-.372c2.388-2.389 2.86-4.951 1.399-7.623l-.912-1.603-.79-1.672c-.26-.56-.194-.98.203-1.288a.7.7 0 0 1 .546-.132c.283.046.546.231.728.5l2.363 4.157c.976 1.624 1.141 4.237-1.324 6.702m-10.999-.438L3.37 14.328a.828.828 0 0 1 .585-1.408.83.83 0 0 1 .585.242l2.158 2.157a.365.365 0 0 0 .516-.516l-2.157-2.158-1.449-1.449a.826.826 0 0 1 1.167-1.17l3.438 3.44a.363.363 0 0 0 .516 0 .364.364 0 0 0 0-.516L5.293 9.513l-.97-.97a.826.826 0 0 1 0-1.166.84.84 0 0 1 1.167 0l.97.968 3.437 3.436a.36.36 0 0 0 .517 0 .366.366 0 0 0 0-.516L6.977 7.83a.82.82 0 0 1-.241-.584.82.82 0 0 1 .824-.826c.219 0 .43.087.584.242l5.787 5.787a.366.366 0 0 0 .587-.415l-1.117-2.363c-.26-.56-.194-.98.204-1.289a.7.7 0 0 1 .546-.132c.283.046.545.232.727.501l2.193 3.86c1.302 2.38.883 4.59-1.277 6.75-1.156 1.156-2.602 1.627-4.19 1.367-1.418-.236-2.866-1.033-4.079-2.246M10.75 5.971l2.12 2.12c-.41.502-.465 1.17-.128 1.89l.22.465-3.523-3.523a.8.8 0 0 1-.097-.368c0-.22.086-.428.241-.584a.847.847 0 0 1 1.167 0m7.355 1.705c-.31-.461-.746-.758-1.23-.837a1.44 1.44 0 0 0-1.11.275c-.312.24-.505.543-.59.881a1.74 1.74 0 0 0-.906-.465 1.47 1.47 0 0 0-.82.106l-2.182-2.182a1.56 1.56 0 0 0-2.2 0 1.54 1.54 0 0 0-.396.701 1.56 1.56 0 0 0-2.21-.01 1.55 1.55 0 0 0-.416.753c-.624-.624-1.649-.624-2.237-.037a1.557 1.557 0 0 0 0 2.2c-.239.1-.501.238-.715.453a1.56 1.56 0 0 0 0 2.2l.516.515a1.556 1.556 0 0 0-.753 2.615L7.01 19c1.32 1.319 2.909 2.189 4.475 2.449q.482.08.971.08c.85 0 1.653-.198 2.393-.579.231.033.46.054.686.054 1.266 0 2.457-.52 3.505-1.567 2.763-2.763 2.552-5.734 1.439-7.586z" clip-rule="evenodd"></path></svg></div></div></div></a></span></div><div class="pw-multi-vote-count l kf kg kh ki kj kk kl"><p class="bf b dv z du"><span class="km">--</span></p></div></div></span></div><div class="bq ab"><div><div class="bm" aria-hidden="false"><button class="ao jx kp kq ab q fk kr ks" aria-label="responses"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="ko"><path d="M18.006 16.803c1.533-1.456 2.234-3.325 2.234-5.321C20.24 7.357 16.709 4 12.191 4S4 7.357 4 11.482c0 4.126 3.674 7.482 8.191 7.482.817 0 1.622-.111 2.393-.327.231.2.48.391.744.559 1.06.693 2.203 1.044 3.399 1.044.224-.008.4-.112.486-.287a.49.49 0 0 0-.042-.518c-.495-.67-.845-1.364-1.04-2.057a4 4 0 0 1-.125-.598zm-3.122 1.055-.067-.223-.315.096a8 8 0 0 1-2.311.338c-4.023 0-7.292-2.955-7.292-6.587 0-3.633 3.269-6.588 7.292-6.588 4.014 0 7.112 2.958 7.112 6.593 0 1.794-.608 3.469-2.027 4.72l-.195.168v.255c0 .056 0 .151.016.295.025.231.081.478.154.733.154.558.398 1.117.722 1.659a5.3 5.3 0 0 1-2.165-.845c-.276-.176-.714-.383-.941-.59z"></path></svg><p class="bf b bg z du"><span class="pw-responses-count kn ko">6</span></p></button></div></div></div></div><div class="ab q"><div class="mu l hu"><div><div class="bm" aria-hidden="false"><span><a class="af ag ah ai aj ak al am an ao ap aq ar as at" data-testid="footerBookmarkButton" rel="noopener follow" href="/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fbc2aebb8bb02&operation=register&redirect=https%3A%2F%2Fmedium.com%2Fflutter-community%2Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02&source=---footer_actions--bc2aebb8bb02---------------------bookmark_footer-----------"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" fill="none" viewBox="0 0 25 25" class="du ku" aria-label="Add to list bookmark button"><path fill="currentColor" d="M18 2.5a.5.5 0 0 1 1 0V5h2.5a.5.5 0 0 1 0 1H19v2.5a.5.5 0 1 1-1 0V6h-2.5a.5.5 0 0 1 0-1H18zM7 7a1 1 0 0 1 1-1h3.5a.5.5 0 0 0 0-1H8a2 2 0 0 0-2 2v14a.5.5 0 0 0 .805.396L12.5 17l5.695 4.396A.5.5 0 0 0 19 21v-8.5a.5.5 0 0 0-1 0v7.485l-5.195-4.012a.5.5 0 0 0-.61 0L7 19.985z"></path></svg></a></span></div></div></div><div class="mu l hu"><div class="bm" aria-hidden="false" aria-describedby="postFooterSocialMenu" aria-labelledby="postFooterSocialMenu"><div><div class="bm" aria-hidden="false"><button aria-controls="postFooterSocialMenu" aria-expanded="false" aria-label="Share Post" data-testid="footerSocialShareButton" class="af fk ah ai aj ak al lc an ao ap ex ld le ks lf"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.218 4.931a.4.4 0 0 1-.118.132l.012.006a.45.45 0 0 1-.292.074.5.5 0 0 1-.3-.13l-2.02-2.02v7.07c0 .28-.23.5-.5.5s-.5-.22-.5-.5v-7.04l-2 2a.45.45 0 0 1-.57.04h-.02a.4.4 0 0 1-.16-.3.4.4 0 0 1 .1-.32l2.8-2.8a.5.5 0 0 1 .7 0l2.8 2.79a.42.42 0 0 1 .068.498m-.106.138.008.004v-.01zM16 7.063h1.5a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-11c-1.1 0-2-.9-2-2v-10a2 2 0 0 1 2-2H8a.5.5 0 0 1 .35.15.5.5 0 0 1 .15.35.5.5 0 0 1-.15.35.5.5 0 0 1-.35.15H6.4c-.5 0-.9.4-.9.9v10.2a.9.9 0 0 0 .9.9h11.2c.5 0 .9-.4.9-.9v-10.2c0-.5-.4-.9-.9-.9H16a.5.5 0 0 1 0-1" clip-rule="evenodd"></path></svg></button></div></div></div></div></div></div></div></div></div></footer><div class="rw rx ry rz sa l"><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="sb bh r sc"></div><div class="sd l"><div class="ab se sf sg hw hv"><div class="sh si sj sk sl sm sn so sp sq ab cp"><div class="h k"><a href="https://medium.com/flutter-community?source=post_page---post_publication_info--bc2aebb8bb02--------------------------------" rel="noopener follow"><div class="fj ab"><img alt="Flutter Community" class="sr gy gz cx" src="https://miro.medium.com/v2/resize:fill:96:96/1*nE4OFcqk2kx2-Lzhey8QKA.png" width="48" height="48" loading="lazy"/><div class="sr l gz gy fs n fr ss"></div></div></a></div><div class="j i d"><a href="https://medium.com/flutter-community?source=post_page---post_publication_info--bc2aebb8bb02--------------------------------" rel="noopener follow"><div class="fj ab"><img alt="Flutter Community" class="sr su st cx" src="https://miro.medium.com/v2/resize:fill:128:128/1*nE4OFcqk2kx2-Lzhey8QKA.png" width="64" height="64" loading="lazy"/><div class="sr l st su fs n fr ss"></div></div></a></div><div class="j i d sv hu"><div class="ab"></div></div></div><div class="ab co sw"><div class="sx sy sz ta tb l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" href="https://medium.com/flutter-community?source=post_page---post_publication_info--bc2aebb8bb02--------------------------------" rel="noopener follow"><h2 class="pw-author-name bf td te tf tg th ti tj of oy oz oj pb pc on pe pf bk"><span class="lu tc">Published in <!-- -->Flutter Community</span></h2></a><div class="rd ab gx"><div class="l hu"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar hn" rel="noopener follow" href="/flutter-community/followers?source=post_page---post_publication_info--bc2aebb8bb02--------------------------------">66K Followers</a></span></div><div class="bf b bg z du ab ia"><span class="ho l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar hn" rel="noopener follow" href="/flutter-community/my-first-step-into-unit-testing-in-flutter-simple-explanations-64e0c4bc4d1a?source=post_page---post_publication_info--bc2aebb8bb02--------------------------------">Last published <span>Nov 12, 2024</span></a></div></div><div class="qc l"><p class="bf b bg z bk"><span class="lu">Articles and Stories from the Flutter Community</span></p></div></div></div><div class="h k"><div class="ab"></div></div></div></div><div class="ab se sf sg hw hv"><div class="sh si sj sk sl sm sn so sp sq ab cp"><div class="h k"><a tabindex="0" rel="noopener follow" href="/@thisisyusub?source=post_page---post_author_info--bc2aebb8bb02--------------------------------"><div class="l fj"><img alt="Kanan Yusubov" class="l fd by gz gy cx" src="https://miro.medium.com/v2/resize:fill:96:96/1*5aYm6yzxghzIfC3A6s2_gA.jpeg" width="48" height="48" loading="lazy"/><div class="fr by l gz gy fs n ay ss"></div></div></a></div><div class="j i d"><a tabindex="0" rel="noopener follow" href="/@thisisyusub?source=post_page---post_author_info--bc2aebb8bb02--------------------------------"><div class="l fj"><img alt="Kanan Yusubov" class="l fd by st su cx" src="https://miro.medium.com/v2/resize:fill:128:128/1*5aYm6yzxghzIfC3A6s2_gA.jpeg" width="64" height="64" loading="lazy"/><div class="fr by l st su fs n ay ss"></div></div></a></div><div class="j i d sv hu"><div class="ab"><span><button class="bf b bg z tk rf tl tm tn to tp ev ew tq tr ts fa fb fc fd bm fe ff">Follow</button></span></div></div></div><div class="ab co sw"><div class="sx sy sz ta tb l"><a class="af ag ah aj ak al am an ao ap aq ar as at ab q" rel="noopener follow" href="/@thisisyusub?source=post_page---post_author_info--bc2aebb8bb02--------------------------------"><h2 class="pw-author-name bf td te tf tg th ti tj of oy oz oj pb pc on pe pf bk"><span class="lu tc">Written by <!-- -->Kanan Yusubov</span></h2></a><div class="rd ab gx"><div class="l hu"><span class="pw-follower-count bf b bg z du"><a class="af ag ah ai aj ak al am an ao ap aq ar hn" rel="noopener follow" href="/@thisisyusub/followers?source=post_page---post_author_info--bc2aebb8bb02--------------------------------">258 Followers</a></span></div><div class="bf b bg z du ab ia"><span class="ho l" aria-hidden="true"><span class="bf b bg z du">·</span></span><a class="af ag ah ai aj ak al am an ao ap aq ar hn" rel="noopener follow" href="/@thisisyusub/following?source=post_page---post_author_info--bc2aebb8bb02--------------------------------">40 Following</a></div></div><div class="qc l"><p class="bf b bg z bk"><span class="lu">Mobile Platforms Expert (Flutter) from Azerbaijan | Founder of Azerbaijan Flutter Users Community</span></p></div></div></div><div class="h k"><div class="ab"><span><button class="bf b bg z tk rf tl tm tn to tp ev ew tq tr ts fa fb fc fd bm fe ff">Follow</button></span></div></div></div></div></div></div><div class="tt tu tv tw tx l"><div class="sb bh r tt tu ty tz ua"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="ab q cp"><h2 class="bf td mz nb nc nd nf ng nh nj nk nl nn no np nr ns bk">Responses (<!-- -->6<!-- -->)</h2><div class="ab ub"><div><div class="bm" aria-hidden="false"><a class="uc ud" href="https://policy.medium.com/medium-rules-30e5502c4eb4?source=post_page---post_responses--bc2aebb8bb02--------------------------------" rel="noopener follow" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25"><path fill-rule="evenodd" d="M11.987 5.036a.754.754 0 0 1 .914-.01c.972.721 1.767 1.218 2.6 1.543.828.322 1.719.485 2.887.505a.755.755 0 0 1 .741.757c-.018 3.623-.43 6.256-1.449 8.21-1.034 1.984-2.662 3.209-4.966 4.083a.75.75 0 0 1-.537-.003c-2.243-.874-3.858-2.095-4.897-4.074-1.024-1.951-1.457-4.583-1.476-8.216a.755.755 0 0 1 .741-.757c1.195-.02 2.1-.182 2.923-.503.827-.322 1.6-.815 2.519-1.535m.468.903c-.897.69-1.717 1.21-2.623 1.564-.898.35-1.856.527-3.026.565.037 3.45.469 5.817 1.36 7.515.884 1.684 2.25 2.762 4.284 3.571 2.092-.81 3.465-1.89 4.344-3.575.886-1.698 1.299-4.065 1.334-7.512-1.149-.039-2.091-.217-2.99-.567-.906-.353-1.745-.873-2.683-1.561m-.009 9.155a2.672 2.672 0 1 0 0-5.344 2.672 2.672 0 0 0 0 5.344m0 1a3.672 3.672 0 1 0 0-7.344 3.672 3.672 0 0 0 0 7.344m-1.813-3.777.525-.526.916.917 1.623-1.625.526.526-2.149 2.152z" clip-rule="evenodd"></path></svg></a></div></div></div></div><div class="mn l"><button class="bf b bg z bk rf ue uf ug ku kr tp ev ew ex uh ui uj fa uk ul um un uo fb fc fd bm fe ff">See all responses</button></div></div></div></div><div class="up uq ur us ut l bx"><div class="h k j"><div class="sb bh uu uv"></div><div class="ab cb"><div class="ci bh fz ga gb gc"><div class="uw ab js hx"><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://help.medium.com/hc/en-us?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Help</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://medium.statuspage.io/?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Status</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/about?autoplay=1&source=post_page-----bc2aebb8bb02--------------------------------"><p class="bf b dv z du">About</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/jobs-at-medium/work-at-medium-959d1a85284e?source=post_page-----bc2aebb8bb02--------------------------------"><p class="bf b dv z du">Careers</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="pressinquiries@medium.com?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Press</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://blog.medium.com/?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Blog</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-privacy-policy-f03bf92035c9?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Privacy</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://policy.medium.com/medium-terms-of-service-9db0094a1e0f?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Terms</p></a></div><div class="ux uy l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" href="https://speechify.com/medium?source=post_page-----bc2aebb8bb02--------------------------------" rel="noopener follow"><p class="bf b dv z du">Text to speech</p></a></div><div class="ux l"><a class="af ag ah ai aj ak al am an ao ap aq ar as at" rel="noopener follow" href="/business?source=post_page-----bc2aebb8bb02--------------------------------"><p class="bf b dv z du">Teams</p></a></div></div></div></div></div></div></div></div></div></div><script>window.__BUILD_ID__="main-20241122-185319-7bcdc08639"</script><script>window.__GRAPHQL_URI__ = "https://medium.com/_/graphql"</script><script>window.__PRELOADED_STATE__ = {"algolia":{"queries":{}},"cache":{"experimentGroupSet":true,"reason":"","group":"enabled","tags":["group-edgeCachePosts","post-bc2aebb8bb02","user-571559932209","collection-86fb29d7cc6a"],"serverVariantState":"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","middlewareEnabled":true,"cacheStatus":"DYNAMIC","shouldUseCache":true,"vary":[],"lohpSummerUpsellEnabled":false,"publicationHierarchyEnabledWeb":false,"postBottomResponsesEnabled":false},"client":{"hydrated":false,"isUs":false,"isNativeMedium":false,"isSafariMobile":false,"isSafari":false,"isFirefox":false,"routingEntity":{"type":"DEFAULT","explicit":false},"viewerIsBot":false},"debug":{"requestId":"075213f3-f31f-410d-8fcd-3033af1066c0","hybridDevServices":[],"originalSpanCarrier":{"traceparent":"00-c69a8ad969f1246b0be025d25569e706-267b9298e87a19a6-01"}},"multiVote":{"clapsPerPost":{}},"navigation":{"branch":{"show":null,"hasRendered":null,"blockedByCTA":false},"hideGoogleOneTap":false,"hasRenderedAlternateUserBanner":null,"currentLocation":"https:\u002F\u002Fmedium.com\u002Fflutter-community\u002Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02","host":"medium.com","hostname":"medium.com","referrer":"","hasSetReferrer":false,"susiModal":{"step":null,"operation":"register"},"postRead":false,"partnerProgram":{"selectedCountryCode":null},"queryString":"","currentHash":""},"config":{"nodeEnv":"production","version":"main-20241122-185319-7bcdc08639","target":"production","productName":"Medium","publicUrl":"https:\u002F\u002Fcdn-client.medium.com\u002Flite","authDomain":"medium.com","authGoogleClientId":"216296035834-k1k6qe060s2tp2a2jam4ljdcms00sttg.apps.googleusercontent.com","favicon":"production","glyphUrl":"https:\u002F\u002Fglyph.medium.com","branchKey":"key_live_ofxXr2qTrrU9NqURK8ZwEhknBxiI6KBm","algolia":{"appId":"MQ57UUUQZ2","apiKeySearch":"394474ced050e3911ae2249ecc774921","indexPrefix":"medium_","host":"-dsn.algolia.net"},"recaptchaKey":"6Lfc37IUAAAAAKGGtC6rLS13R1Hrw_BqADfS1LRk","recaptcha3Key":"6Lf8R9wUAAAAABMI_85Wb8melS7Zj6ziuf99Yot5","recaptchaEnterpriseKeyId":"6Le-uGgpAAAAAPprRaokM8AKthQ9KNGdoxaGUvVp","datadog":{"applicationId":"6702d87d-a7e0-42fe-bbcb-95b469547ea0","clientToken":"pub853ea8d17ad6821d9f8f11861d23dfed","rumToken":"pubf9cc52896502b9413b68ba36fc0c7162","context":{"deployment":{"target":"production","tag":"main-20241122-185319-7bcdc08639","commit":"7bcdc08639c179dc5172558201a3fd3abc1b5db6"}},"datacenter":"us"},"googleAnalyticsCode":"G-7JY7T788PK","googlePay":{"apiVersion":"2","apiVersionMinor":"0","merchantId":"BCR2DN6TV7EMTGBM","merchantName":"Medium","instanceMerchantId":"13685562959212738550"},"applePay":{"version":3},"signInWallCustomDomainCollectionIds":["3a8144eabfe3","336d898217ee","61061eb0c96b","138adf9c44c","819cc2aaeee0"],"mediumMastodonDomainName":"me.dm","mediumOwnedAndOperatedCollectionIds":["8a9336e5bb4","b7e45b22fec3","193b68bd4fba","8d6b8a439e32","54c98c43354d","3f6ecf56618","d944778ce714","92d2092dc598","ae2a65f35510","1285ba81cada","544c7006046e","fc8964313712","40187e704f1c","88d9857e584e","7b6769f2748b","bcc38c8f6edf","cef6983b292","cb8577c9149e","444d13b52878","713d7dbc99b0","ef8e90590e66","191186aaafa0","55760f21cdc5","9dc80918cc93","bdc4052bbdba","8ccfed20cbb2"],"tierOneDomains":["medium.com","thebolditalic.com","arcdigital.media","towardsdatascience.com","uxdesign.cc","codeburst.io","psiloveyou.xyz","writingcooperative.com","entrepreneurshandbook.co","prototypr.io","betterhumans.coach.me","theascent.pub"],"topicsToFollow":["d61cf867d93f","8a146bc21b28","1eca0103fff3","4d562ee63426","aef1078a3ef5","e15e46793f8d","6158eb913466","55f1c20aba7a","3d18b94f6858","4861fee224fd","63c6f1f93ee","1d98b3a9a871","decb52b64abf","ae5d4995e225","830cded25262"],"topicToTagMappings":{"accessibility":"accessibility","addiction":"addiction","android-development":"android-development","art":"art","artificial-intelligence":"artificial-intelligence","astrology":"astrology","basic-income":"basic-income","beauty":"beauty","biotech":"biotech","blockchain":"blockchain","books":"books","business":"business","cannabis":"cannabis","cities":"cities","climate-change":"climate-change","comics":"comics","coronavirus":"coronavirus","creativity":"creativity","cryptocurrency":"cryptocurrency","culture":"culture","cybersecurity":"cybersecurity","data-science":"data-science","design":"design","digital-life":"digital-life","disability":"disability","economy":"economy","education":"education","equality":"equality","family":"family","feminism":"feminism","fiction":"fiction","film":"film","fitness":"fitness","food":"food","freelancing":"freelancing","future":"future","gadgets":"gadgets","gaming":"gaming","gun-control":"gun-control","health":"health","history":"history","humor":"humor","immigration":"immigration","ios-development":"ios-development","javascript":"javascript","justice":"justice","language":"language","leadership":"leadership","lgbtqia":"lgbtqia","lifestyle":"lifestyle","machine-learning":"machine-learning","makers":"makers","marketing":"marketing","math":"math","media":"media","mental-health":"mental-health","mindfulness":"mindfulness","money":"money","music":"music","neuroscience":"neuroscience","nonfiction":"nonfiction","outdoors":"outdoors","parenting":"parenting","pets":"pets","philosophy":"philosophy","photography":"photography","podcasts":"podcast","poetry":"poetry","politics":"politics","privacy":"privacy","product-management":"product-management","productivity":"productivity","programming":"programming","psychedelics":"psychedelics","psychology":"psychology","race":"race","relationships":"relationships","religion":"religion","remote-work":"remote-work","san-francisco":"san-francisco","science":"science","self":"self","self-driving-cars":"self-driving-cars","sexuality":"sexuality","social-media":"social-media","society":"society","software-engineering":"software-engineering","space":"space","spirituality":"spirituality","sports":"sports","startups":"startup","style":"style","technology":"technology","transportation":"transportation","travel":"travel","true-crime":"true-crime","tv":"tv","ux":"ux","venture-capital":"venture-capital","visual-design":"visual-design","work":"work","world":"world","writing":"writing"},"defaultImages":{"avatar":{"imageId":"1*dmbNkD5D-u45r44go_cf0g.png","height":150,"width":150},"orgLogo":{"imageId":"7*V1_7XP4snlmqrc_0Njontw.png","height":110,"width":500},"postLogo":{"imageId":"bd978bb536350a710e8efb012513429cabdc4c28700604261aeda246d0f980b7","height":810,"width":1440},"postPreviewImage":{"imageId":"1*hn4v1tCaJy7cWMyb0bpNpQ.png","height":386,"width":579}},"collectionStructuredData":{"8d6b8a439e32":{"name":"Elemental","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F980\u002F1*9ygdqoKprhwuTVKUM0DLPA@2x.png","width":980,"height":159}}},"3f6ecf56618":{"name":"Forge","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F596\u002F1*uULpIlImcO5TDuBZ6lm7Lg@2x.png","width":596,"height":183}}},"ae2a65f35510":{"name":"GEN","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F264\u002F1*RdVZMdvfV3YiZTw6mX7yWA.png","width":264,"height":140}}},"88d9857e584e":{"name":"LEVEL","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*JqYMhNX6KNNb2UlqGqO2WQ.png","width":540,"height":108}}},"7b6769f2748b":{"name":"Marker","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F383\u002F1*haCUs0wF6TgOOvfoY-jEoQ@2x.png","width":383,"height":92}}},"444d13b52878":{"name":"OneZero","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*cw32fIqCbRWzwJaoQw6BUg.png","width":540,"height":123}}},"8ccfed20cbb2":{"name":"Zora","data":{"@type":"NewsMediaOrganization","ethicsPolicy":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Farticles\u002F360043290473","logo":{"@type":"ImageObject","url":"https:\u002F\u002Fmiro.medium.com\u002Fmax\u002F540\u002F1*tZUQqRcCCZDXjjiZ4bDvgQ.png","width":540,"height":106}}}},"embeddedPostIds":{"coronavirus":"cd3010f9d81f"},"sharedCdcMessaging":{"COVID_APPLICABLE_TAG_SLUGS":[],"COVID_APPLICABLE_TOPIC_NAMES":[],"COVID_APPLICABLE_TOPIC_NAMES_FOR_TOPIC_PAGE":[],"COVID_MESSAGES":{"tierA":{"text":"For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":66,"end":73,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"tierB":{"text":"Anyone can publish on Medium per our Policies, but we don’t fact-check every story. For more info about the coronavirus, see cdc.gov.","markups":[{"start":37,"end":45,"href":"https:\u002F\u002Fhelp.medium.com\u002Fhc\u002Fen-us\u002Fcategories\u002F201931128-Policies-Safety"},{"start":125,"end":132,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"paywall":{"text":"This article has been made free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":56,"end":70,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":138,"end":145,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]},"unbound":{"text":"This article is free for everyone, thanks to Medium Members. For more information on the novel coronavirus and Covid-19, visit cdc.gov.","markups":[{"start":45,"end":59,"href":"https:\u002F\u002Fmedium.com\u002Fmembership"},{"start":127,"end":134,"href":"https:\u002F\u002Fwww.cdc.gov\u002Fcoronavirus\u002F2019-nCoV"}]}},"COVID_BANNER_POST_ID_OVERRIDE_WHITELIST":["3b31a67bff4a"]},"sharedVoteMessaging":{"TAGS":["politics","election-2020","government","us-politics","election","2020-presidential-race","trump","donald-trump","democrats","republicans","congress","republican-party","democratic-party","biden","joe-biden","maga"],"TOPICS":["politics","election"],"MESSAGE":{"text":"Find out more about the U.S. election results here.","markups":[{"start":46,"end":50,"href":"https:\u002F\u002Fcookpolitical.com\u002F2020-national-popular-vote-tracker"}]},"EXCLUDE_POSTS":["397ef29e3ca5"]},"embedPostRules":[],"recircOptions":{"v1":{"limit":3},"v2":{"limit":8}},"braintreeClientKey":"production_zjkj96jm_m56f8fqpf7ngnrd4","braintree":{"enabled":true,"merchantId":"m56f8fqpf7ngnrd4","merchantAccountId":{"usd":"AMediumCorporation_instant","eur":"amediumcorporation_EUR","cad":"amediumcorporation_CAD"},"publicKey":"ds2nn34bg2z7j5gd","braintreeEnvironment":"production","dashboardUrl":"https:\u002F\u002Fwww.braintreegateway.com\u002Fmerchants","gracePeriodDurationInDays":14,"mediumMembershipPlanId":{"monthly":"ce105f8c57a3","monthlyV2":"e8a5e126-792b-4ee6-8fba-d574c1b02fc5","monthlyWithTrial":"d5ee3dbe3db8","monthlyPremium":"fa741a9b47a2","yearly":"a40ad4a43185","yearlyV2":"3815d7d6-b8ca-4224-9b8c-182f9047866e","yearlyStaff":"d74fb811198a","yearlyWithTrial":"b3bc7350e5c7","yearlyPremium":"e21bd2c12166","monthlyOneYearFree":"e6c0637a-2bad-4171-ab4f-3c268633d83c","monthly25PercentOffFirstYear":"235ecc62-0cdb-49ae-9378-726cd21c504b","monthly20PercentOffFirstYear":"ba518864-9c13-4a99-91ca-411bf0cac756","monthly15PercentOffFirstYear":"594c029b-9f89-43d5-88f8-8173af4e070e","monthly10PercentOffFirstYear":"c6c7bc9a-40f2-4b51-8126-e28511d5bdb0","monthlyForStudents":"629ebe51-da7d-41fd-8293-34cd2f2030a8","yearlyOneYearFree":"78ba7be9-0d9f-4ece-aa3e-b54b826f2bf1","yearly25PercentOffFirstYear":"2dbb010d-bb8f-4eeb-ad5c-a08509f42d34","yearly20PercentOffFirstYear":"47565488-435b-47f8-bf93-40d5fbe0ebc8","yearly15PercentOffFirstYear":"8259809b-0881-47d9-acf7-6c001c7f720f","yearly10PercentOffFirstYear":"9dd694fb-96e1-472c-8d9e-3c868d5c1506","yearlyForStudents":"e29345ef-ab1c-4234-95c5-70e50fe6bc23","monthlyCad":"p52orjkaceei","yearlyCad":"h4q9g2up9ktt"},"braintreeDiscountId":{"oneMonthFree":"MONTHS_FREE_01","threeMonthsFree":"MONTHS_FREE_03","sixMonthsFree":"MONTHS_FREE_06","fiftyPercentOffOneYear":"FIFTY_PERCENT_OFF_ONE_YEAR"},"3DSecureVersion":"2","defaultCurrency":"usd","providerPlanIdCurrency":{"4ycw":"usd","rz3b":"usd","3kqm":"usd","jzw6":"usd","c2q2":"usd","nnsw":"usd","q8qw":"usd","d9y6":"usd","fx7w":"cad","nwf2":"cad"}},"paypalClientId":"AXj1G4fotC2GE8KzWX9mSxCH1wmPE3nJglf4Z2ig_amnhvlMVX87otaq58niAg9iuLktVNF_1WCMnN7v","paypal":{"host":"https:\u002F\u002Fapi.paypal.com:443","clientMode":"production","serverMode":"live","webhookId":"4G466076A0294510S","monthlyPlan":{"planId":"P-9WR0658853113943TMU5FDQA","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlan":{"planId":"P-7N8963881P8875835MU5JOPQ","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oneYearGift":{"name":"Medium Membership (1 Year, Digital Gift Code)","description":"Unlimited access to the best and brightest stories on Medium. Gift codes can be redeemed at medium.com\u002Fredeem.","price":"50.00","currency":"USD","sku":"membership-gift-1-yr"},"oldMonthlyPlan":{"planId":"P-96U02458LM656772MJZUVH2Y","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlan":{"planId":"P-59P80963JF186412JJZU3SMI","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"monthlyPlanWithTrial":{"planId":"P-66C21969LR178604GJPVKUKY","name":"Medium Membership (Monthly) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"yearlyPlanWithTrial":{"planId":"P-6XW32684EX226940VKCT2MFA","name":"Medium Membership (Annual) with setup fee","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"oldMonthlyPlanNoSetupFee":{"planId":"P-4N046520HR188054PCJC7LJI","name":"Medium Membership (Monthly)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed monthly."},"oldYearlyPlanNoSetupFee":{"planId":"P-7A4913502Y5181304CJEJMXQ","name":"Medium Membership (Annual)","description":"Unlimited access to the best and brightest stories on Medium. Membership billed annually."},"sdkUrl":"https:\u002F\u002Fwww.paypal.com\u002Fsdk\u002Fjs"},"stripePublishableKey":"pk_live_7FReX44VnNIInZwrIIx6ghjl","log":{"json":true,"level":"info"},"imageUploadMaxSizeMb":25,"staffPicks":{"title":"Staff Picks","catalogId":"c7bc6e1ee00f"}},"session":{"xsrf":""}}</script><script>window.__APOLLO_STATE__ = {"ROOT_QUERY":{"__typename":"Query","collectionByDomainOrSlug({\"domainOrSlug\":\"flutter-community\"})":{"__ref":"Collection:86fb29d7cc6a"},"viewer":null,"postResult({\"id\":\"bc2aebb8bb02\"})":{"__ref":"Post:bc2aebb8bb02"}},"Collection:86fb29d7cc6a":{"__typename":"Collection","id":"86fb29d7cc6a","customStyleSheet":null,"colorPalette":{"__typename":"ColorPalette","highlightSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FFE2F6FF","point":0},{"__typename":"ColorPoint","color":"#FFDDF5FF","point":0.1},{"__typename":"ColorPoint","color":"#FFD7F4FF","point":0.2},{"__typename":"ColorPoint","color":"#FFD1F2FF","point":0.3},{"__typename":"ColorPoint","color":"#FFCBF1FF","point":0.4},{"__typename":"ColorPoint","color":"#FFC4F0FF","point":0.5},{"__typename":"ColorPoint","color":"#FFBEEFFF","point":0.6},{"__typename":"ColorPoint","color":"#FFB7EEFF","point":0.7},{"__typename":"ColorPoint","color":"#FFAFECFF","point":0.8},{"__typename":"ColorPoint","color":"#FFA8EBFF","point":0.9},{"__typename":"ColorPoint","color":"#FFA0EAFF","point":1}]},"defaultBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FFFFFFFF","colorPoints":[{"__typename":"ColorPoint","color":"#FF008EE7","point":0},{"__typename":"ColorPoint","color":"#FF1184D3","point":0.1},{"__typename":"ColorPoint","color":"#FF1C7AC0","point":0.2},{"__typename":"ColorPoint","color":"#FF2270AD","point":0.3},{"__typename":"ColorPoint","color":"#FF256599","point":0.4},{"__typename":"ColorPoint","color":"#FF255A87","point":0.5},{"__typename":"ColorPoint","color":"#FF244F74","point":0.6},{"__typename":"ColorPoint","color":"#FF214361","point":0.7},{"__typename":"ColorPoint","color":"#FF1C364E","point":0.8},{"__typename":"ColorPoint","color":"#FF162A3B","point":0.9},{"__typename":"ColorPoint","color":"#FF0F1C28","point":1}]},"tintBackgroundSpectrum":{"__typename":"ColorSpectrum","backgroundColor":"#FF0091EA","colorPoints":[{"__typename":"ColorPoint","color":"#FF0091EA","point":0},{"__typename":"ColorPoint","color":"#FF3C9EEE","point":0.1},{"__typename":"ColorPoint","color":"#FF59AAF3","point":0.2},{"__typename":"ColorPoint","color":"#FF71B6F7","point":0.3},{"__typename":"ColorPoint","color":"#FF86C2FB","point":0.4},{"__typename":"ColorPoint","color":"#FF9ACDFF","point":0.5},{"__typename":"ColorPoint","color":"#FFADD8FF","point":0.6},{"__typename":"ColorPoint","color":"#FFBFE3FF","point":0.7},{"__typename":"ColorPoint","color":"#FFD1EDFF","point":0.8},{"__typename":"ColorPoint","color":"#FFE2F8FF","point":0.9},{"__typename":"ColorPoint","color":"#FFF2FFFF","point":1}]}},"favicon":{"__ref":"ImageMetadata:"},"domain":null,"slug":"flutter-community","googleAnalyticsId":null,"editors":[{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:91cb19edfc82"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:92720848ff49"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:8981f0c624ae"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:42f7c0123db5"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:73db6ccecfe2"}},{"__typename":"CollectionMastheadUserItem","user":{"__ref":"User:bf9bc82a0ba9"}}],"name":"Flutter Community","avatar":{"__ref":"ImageMetadata:1*nE4OFcqk2kx2-Lzhey8QKA.png"},"description":"Articles and Stories from the Flutter Community","subscriberCount":66982,"latestPostsConnection({\"paging\":{\"limit\":1}})":{"__typename":"PostConnection","posts":[{"__ref":"Post:64e0c4bc4d1a"}]},"viewerEdge":{"__ref":"CollectionViewerEdge:collectionId:86fb29d7cc6a-viewerId:lo_9a13a25784b9"},"twitterUsername":"FlutterComm","facebookPageId":null,"logo":{"__ref":"ImageMetadata:1*4JWvD8Buzbf7cA6EfRvkiA.png"}},"ImageMetadata:":{"__typename":"ImageMetadata","id":""},"User:91cb19edfc82":{"__typename":"User","id":"91cb19edfc82"},"User:92720848ff49":{"__typename":"User","id":"92720848ff49"},"User:8981f0c624ae":{"__typename":"User","id":"8981f0c624ae"},"User:42f7c0123db5":{"__typename":"User","id":"42f7c0123db5"},"User:73db6ccecfe2":{"__typename":"User","id":"73db6ccecfe2"},"User:bf9bc82a0ba9":{"__typename":"User","id":"bf9bc82a0ba9"},"ImageMetadata:1*nE4OFcqk2kx2-Lzhey8QKA.png":{"__typename":"ImageMetadata","id":"1*nE4OFcqk2kx2-Lzhey8QKA.png"},"User:2d341a775164":{"__typename":"User","id":"2d341a775164","customDomainState":null,"hasSubdomain":false,"username":"surajbhandari5502"},"Post:64e0c4bc4d1a":{"__typename":"Post","id":"64e0c4bc4d1a","firstPublishedAt":1731425568345,"creator":{"__ref":"User:2d341a775164"},"collection":{"__ref":"Collection:86fb29d7cc6a"},"isSeries":false,"mediumUrl":"https:\u002F\u002Fmedium.com\u002Fflutter-community\u002Fmy-first-step-into-unit-testing-in-flutter-simple-explanations-64e0c4bc4d1a","sequence":null,"uniqueSlug":"my-first-step-into-unit-testing-in-flutter-simple-explanations-64e0c4bc4d1a"},"LinkedAccounts:571559932209":{"__typename":"LinkedAccounts","mastodon":null,"id":"571559932209"},"UserViewerEdge:userId:571559932209-viewerId:lo_9a13a25784b9":{"__typename":"UserViewerEdge","id":"userId:571559932209-viewerId:lo_9a13a25784b9","isFollowing":false,"isUser":false,"isMuting":false},"NewsletterV3:e445750af20a":{"__typename":"NewsletterV3","id":"e445750af20a","type":"NEWSLETTER_TYPE_AUTHOR","slug":"571559932209","name":"571559932209","collection":null,"user":{"__ref":"User:571559932209"}},"User:571559932209":{"__typename":"User","id":"571559932209","name":"Kanan Yusubov","username":"thisisyusub","newsletterV3":{"__ref":"NewsletterV3:e445750af20a"},"linkedAccounts":{"__ref":"LinkedAccounts:571559932209"},"isSuspended":false,"imageId":"1*5aYm6yzxghzIfC3A6s2_gA.jpeg","mediumMemberAt":0,"verifications":{"__typename":"VerifiedInfo","isBookAuthor":false},"socialStats":{"__typename":"SocialStats","followerCount":258,"followingCount":29,"collectionFollowingCount":11},"customDomainState":{"__typename":"CustomDomainState","live":{"__typename":"CustomDomain","domain":"yusubov.com"}},"hasSubdomain":true,"bio":"Mobile Platforms Expert (Flutter) from Azerbaijan | Founder of Azerbaijan Flutter Users Community","isPartnerProgramEnrolled":true,"viewerEdge":{"__ref":"UserViewerEdge:userId:571559932209-viewerId:lo_9a13a25784b9"},"viewerIsUser":false,"postSubscribeMembershipUpsellShownAt":0,"membership":null,"allowNotes":true,"twitterScreenName":""},"ImageMetadata:1*CtlNPhgJx2QqxiDGQ8kNNg.png":{"__typename":"ImageMetadata","id":"1*CtlNPhgJx2QqxiDGQ8kNNg.png","originalHeight":1080,"originalWidth":1920,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_0":{"__typename":"Paragraph","id":"aeb6a299c0b1_0","name":"26f8","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*CtlNPhgJx2QqxiDGQ8kNNg.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_1":{"__typename":"Paragraph","id":"aeb6a299c0b1_1","name":"4d22","type":"H3","href":null,"layout":null,"metadata":null,"text":"Design System from scratch in Flutter","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_2":{"__typename":"Paragraph","id":"aeb6a299c0b1_2","name":"c22b","type":"P","href":null,"layout":null,"metadata":null,"text":"Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design System, which we have implemented in our projects before.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_3":{"__typename":"Paragraph","id":"aeb6a299c0b1_3","name":"1d81","type":"H4","href":null,"layout":null,"metadata":null,"text":"Why do we need a Design System?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_4":{"__typename":"Paragraph","id":"aeb6a299c0b1_4","name":"b947","type":"P","href":null,"layout":null,"metadata":null,"text":"In our example, we have applied it to share our design code between mobile, web, and desktop apps. As a result, it is an independent package working on its own. We can inject into any project in a few steps.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_5":{"__typename":"Paragraph","id":"aeb6a299c0b1_5","name":"b9e2","type":"H4","href":null,"layout":null,"metadata":null,"text":"Let’s start with the atomic parts.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_6":{"__typename":"Paragraph","id":"aeb6a299c0b1_6","name":"68d9","type":"P","href":null,"layout":null,"metadata":null,"text":"As a first step, we divided all minor parts — colors, radiuses, shadows, etc into independent classes. Definitely, Implemented design system codes depend on the designer’s implementation.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*zUbtS5Uz9TkVtGabxZI4aA.png":{"__typename":"ImageMetadata","id":"1*zUbtS5Uz9TkVtGabxZI4aA.png","originalHeight":570,"originalWidth":1432,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_7":{"__typename":"Paragraph","id":"aeb6a299c0b1_7","name":"495b","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*zUbtS5Uz9TkVtGabxZI4aA.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_8":{"__typename":"Paragraph","id":"aeb6a299c0b1_8","name":"0d36","type":"P","href":null,"layout":null,"metadata":null,"text":"As you can see, the designer divided atomic parts for us (thanks to our designer)","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":72,"end":80,"href":"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgadirli\u002F","anchorType":"LINK","userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_9":{"__typename":"Paragraph","id":"aeb6a299c0b1_9","name":"8847","type":"P","href":null,"layout":null,"metadata":null,"text":"I will not mention all the code; it will be snippets. They are like the following examples:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_10":{"__typename":"Paragraph","id":"aeb6a299c0b1_10","name":"3bd9","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_colors}\n\u002F\u002F\u002F Colors class for themes which provides direct access with static fields.\n\u002F\u002F\u002F {@endtemplate}\nclass AppColors {\n AppColors._();\n\n \u002F\u002F\u002F The color white\n static const white = Colors.white;\n\n \u002F\u002F\u002F The color black\n static const black = Colors.black;\n\n \u002F\u002F\u002F The color transparent\n static const transparent = Colors.transparent;\n\n \u002F\u002F\u002F Brand color palette.\n static const brand = MaterialColor(\n 0xFF347AF6,\n {\n 50: Color(0xFFF0F5FF),\n 100: Color(0xFFE0ECFF),\n 150: Color(0xFFD3E1FB),\n 200: Color(0xFFBDD3F9),\n 250: Color(0xFF9FBFF9),\n 300: Color(0xFF81ACF9),\n 400: Color(0xFF5A93F9),\n 500: Color(0xFF347AF6),\n 600: Color(0xFF1559D1),\n 700: Color(0xFF174EAF),\n 800: Color(0xFF1D4387),\n 900: Color(0xFF163367),\n },\n );\n\n \u002F\u002F\u002F Light gray color palette.\n static const grayLight = MaterialColor(\n 0xFF667085,\n {\n 50: Color(0xFFFCFCFD),\n 100: Color(0xFFF9FAFB),\n 150: Color(0xFFF2F4F7),\n 200: Color(0xFFEAECF0),\n 250: Color(0xFFD0D5DD),\n 300: Color(0xFF98A2B3),\n 400: Color(0xFF667085),\n 500: Color(0xFF475467),\n 600: Color(0xFF344054),\n 700: Color(0xFF182230),\n 800: Color(0xFF101828),\n 900: Color(0xFF0C111D),\n },\n );\n\n \u002F\u002F\u002F Dark gray color palette.\n static const grayDark = MaterialColor(\n 0xFF85888E,\n {\n 50: Color(0xFFFAFAFA),\n 100: Color(0xFFF5F5F6),\n 150: Color(0xFFF0F1F1),\n 200: Color(0xFFECECED),\n 250: Color(0xFFCECFD2),\n 300: Color(0xFF94969C),\n 400: Color(0xFF85888E),\n 500: Color(0xFF61646C),\n 600: Color(0xFF333741),\n 700: Color(0xFF1F242F),\n 800: Color(0xFF161B26),\n 900: Color(0xFF0C111D),\n },\n );","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_11":{"__typename":"Paragraph","id":"aeb6a299c0b1_11","name":"7d64","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_radius}\n\u002F\u002F\u002F Radius class contains all radius used in app\n\u002F\u002F\u002F {@endtemplate}\nclass AppRadius {\n AppRadius._();\n\n \u002F\u002F\u002F Radius of 0.\n static const none = Radius.zero;\n\n \u002F\u002F\u002F Extra extra small radius of 2.\n static const xxs = Radius.circular(2);\n\n \u002F\u002F\u002F Extra small radius of 4.\n static const xs = Radius.circular(4);\n\n \u002F\u002F\u002F Small radius of 6.\n static const sm = Radius.circular(6);\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_12":{"__typename":"Paragraph","id":"aeb6a299c0b1_12","name":"ef47","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_shadow}\n\u002F\u002F\u002F Shadow class contains all shadows used in app\n\u002F\u002F\u002F {@endtemplate}\nclass AppShadow {\n AppShadow._();\n\n\n \u002F\u002F\u002F Extra small shadow.\n static const xs = [\n BoxShadow(\n blurRadius: 2,\n offset: Offset(0, 1),\n color: Color.fromRGBO(16, 24, 40, 0.05),\n ),\n ];\n\n \u002F\u002F\u002F Small shadow.\n static const sm = [\n BoxShadow(\n color: Color(0x0F101828),\n blurRadius: 2,\n offset: Offset(0, 1),\n ),\n BoxShadow(\n color: Color(0x19101828),\n blurRadius: 3,\n offset: Offset(0, 1),\n ),\n ];\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_13":{"__typename":"Paragraph","id":"aeb6a299c0b1_13","name":"d4c2","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_spacing}\n\u002F\u002F\u002F Class contains all space (does not matter is it vertical\n\u002F\u002F\u002F or horizontal used in app\n\u002F\u002F\u002F {@endtemplate}\nclass AppSpacing {\n AppSpacing._();\n\n \u002F\u002F\u002F No spacing.\n static const none = 0.0;\n\n \u002F\u002F\u002F Extra extra small spacing of 2.0.\n static const xxs = 2.0;\n\n \u002F\u002F\u002F Extra small spacing of 4.0.\n static const xs = 4.0;\n\n \u002F\u002F\u002F Small spacing of 6.0.\n static const sm = 6.0;","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_14":{"__typename":"Paragraph","id":"aeb6a299c0b1_14","name":"01c7","type":"BQ","href":null,"layout":null,"metadata":null,"text":"You can have more atomic parts in your projects.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":48,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_15":{"__typename":"Paragraph","id":"aeb6a299c0b1_15","name":"87b5","type":"H4","href":null,"layout":null,"metadata":null,"text":"Next stage — components","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_16":{"__typename":"Paragraph","id":"aeb6a299c0b1_16","name":"e4bf","type":"P","href":null,"layout":null,"metadata":null,"text":"I will show you some of our components — like buttons, textfield. Other components developed in the same way.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_17":{"__typename":"Paragraph","id":"aeb6a299c0b1_17","name":"6b11","type":"H4","href":null,"layout":null,"metadata":null,"text":"Theme development for component","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_18":{"__typename":"Paragraph","id":"aeb6a299c0b1_18","name":"2d41","type":"P","href":null,"layout":null,"metadata":null,"text":"I have written a new theme class for each of our components to keep them more clean. Sure, it is provided by our designer, too :)","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":85,"end":129,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"EM","start":85,"end":129,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*X_JQ7ICywiR7skrp8vMNUg.png":{"__typename":"ImageMetadata","id":"1*X_JQ7ICywiR7skrp8vMNUg.png","originalHeight":528,"originalWidth":820,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_19":{"__typename":"Paragraph","id":"aeb6a299c0b1_19","name":"d501","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*X_JQ7ICywiR7skrp8vMNUg.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_20":{"__typename":"Paragraph","id":"aeb6a299c0b1_20","name":"5ca7","type":"BQ","href":null,"layout":null,"metadata":null,"text":"Keep in mind that Theme classes are extended from ThemeExtension, in this way, we can register them as theme extensions and use them with Theme class.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":50,"end":64,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_21":{"__typename":"Paragraph","id":"aeb6a299c0b1_21","name":"3958","type":"P","href":null,"layout":null,"metadata":null,"text":"For instance, we can check Theme classes for buttons and text fields:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_22":{"__typename":"Paragraph","id":"aeb6a299c0b1_22","name":"6557","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_button_theme}\n\u002F\u002F\u002F Theme class which provides configuration of buttons\n\u002F\u002F\u002F {@endtemplate}\nclass AppButtonTheme extends ThemeExtension\u003CAppButtonTheme\u003E {\n \u002F\u002F\u002F {@macro app_button_theme}\n const AppButtonTheme({\n required this.primaryText,\n required this.primaryDefault,\n required this.primaryHover,\n required this.primaryFocused,\n });\n\n \u002F\u002F\u002F {@macro app_button_theme}\n factory AppButtonTheme.light() {\n return AppButtonTheme(\n primaryText: AppColors.white,\n primaryDefault: AppColors.brand.shade500,\n primaryHover: AppColors.brand.shade600,\n primaryFocused: AppColors.brand.shade700,\n );\n }\n\n \u002F\u002F\u002F The color of the primary text.\n final Color primaryText;\n\n \u002F\u002F\u002F The color of the primary button default.\n final Color primaryDefault;\n\n \u002F\u002F\u002F The color of the primary button hover.\n final Color primaryHover;\n\n \u002F\u002F\u002F The color of the primary button focused.\n final Color primaryFocused;\n\n @override\n ThemeExtension\u003CAppButtonTheme\u003E copyWith({\n Color? primaryText,\n Color? primaryDefault,\n Color? primaryHover,\n Color? primaryFocused,\n }) {\n return AppButtonTheme(\n primaryText: primaryText ?? this.primaryText,\n primaryDefault: primaryDefault ?? this.primaryDefault,\n primaryHover: primaryHover ?? this.primaryHover,\n primaryFocused: primaryFocused ?? this.primaryFocused,\n );\n }\n\n @override\n ThemeExtension\u003CAppButtonTheme\u003E lerp(\n covariant ThemeExtension\u003CAppButtonTheme\u003E? other,\n double t,\n ) {\n if (other is! AppButtonTheme) {\n return this;\n }\n\n return AppButtonTheme(\n primaryText: Color.lerp(primaryText, other.primaryText, t)!,\n primaryDefault: Color.lerp(primaryDefault, other.primaryDefault, t)!,\n primaryHover: Color.lerp(primaryHover, other.primaryHover, t)!,\n primaryFocused: Color.lerp(primaryFocused, other.primaryFocused, t)!,\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_23":{"__typename":"Paragraph","id":"aeb6a299c0b1_23","name":"897f","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_input_theme}\n\u002F\u002F\u002F Theme class which provides configuration of [AppTextField]\n\u002F\u002F\u002F {@endtemplate}\nclass AppInputTheme extends ThemeExtension\u003CAppInputTheme\u003E {\n \u002F\u002F\u002F {@macro app_input_theme}\n const AppInputTheme({\n required this.defaultText,\n required this.focusedOnBrand,\n required this.focusedTextDefault,\n required this.errorTextDefault,\n required this.successTextDefault,\n required this.disabledText,\n required this.borderDefault,\n required this.borderHover,\n required this.borderFocused,\n required this.borderError,\n required this.borderSuccess,\n required this.borderDisabled,\n required this.defaultColor,\n required this.disabledColor,\n });\n\n \u002F\u002F\u002F {@macro app_input_theme}\n factory AppInputTheme.light() {\n return AppInputTheme(\n defaultText: AppColors.grayLight.shade400,\n focusedOnBrand: AppColors.brand.shade500,\n focusedTextDefault: AppColors.grayLight.shade600,\n errorTextDefault: AppColors.error.shade400,\n successTextDefault: AppColors.success.shade400,\n disabledText: AppColors.grayLight[250]!,\n borderDefault: AppColors.grayLight[250]!,\n borderHover: AppColors.grayLight.shade300,\n borderFocused: AppColors.brand.shade500,\n borderError: AppColors.error.shade400,\n borderSuccess: AppColors.success.shade400,\n borderDisabled: AppColors.grayLight.shade200,\n defaultColor: AppColors.white,\n disabledColor: AppColors.grayLight.shade100,\n );\n }\n\n \u002F\u002F\u002F The default text color.\n final Color defaultText;\n\n \u002F\u002F\u002F The text color when focused on brand.\n final Color focusedOnBrand;\n\n \u002F\u002F\u002F The text color when focused.\n final Color focusedTextDefault;\n\n \u002F\u002F\u002F The text color when error.\n final Color errorTextDefault;\n\n \u002F\u002F\u002F The text color when success.\n final Color successTextDefault;\n\n \u002F\u002F\u002F The text color when disabled.\n final Color disabledText;\n\n \u002F\u002F\u002F The default border color.\n final Color borderDefault;\n\n \u002F\u002F\u002F The border color when hovered.\n final Color borderHover;\n\n \u002F\u002F\u002F The border color when focused.\n final Color borderFocused;\n\n \u002F\u002F\u002F The border color when error.\n final Color borderError;\n\n \u002F\u002F\u002F The border color when success.\n final Color borderSuccess;\n\n \u002F\u002F\u002F The border color when disabled.\n final Color borderDisabled;\n\n \u002F\u002F\u002F The default color.\n final Color defaultColor;\n\n \u002F\u002F\u002F The disabled color.\n final Color disabledColor;\n\n @override\n ThemeExtension\u003CAppInputTheme\u003E copyWith({\n Color? defaultText,\n Color? focusedOnBrand,\n Color? focusedTextDefault,\n Color? errorTextDefault,\n Color? successTextDefault,\n Color? disabledText,\n Color? borderDefault,\n Color? borderHover,\n Color? borderFocused,\n Color? borderError,\n Color? borderSuccess,\n Color? borderDisabled,\n Color? defaultColor,\n Color? disabledColor,\n }) {\n return AppInputTheme(\n defaultText: defaultText ?? this.defaultText,\n focusedOnBrand: focusedOnBrand ?? this.focusedOnBrand,\n focusedTextDefault: focusedTextDefault ?? this.focusedTextDefault,\n errorTextDefault: errorTextDefault ?? this.errorTextDefault,\n successTextDefault: successTextDefault ?? this.successTextDefault,\n disabledText: disabledText ?? this.disabledText,\n borderDefault: borderDefault ?? this.borderDefault,\n borderHover: borderHover ?? this.borderHover,\n borderFocused: borderFocused ?? this.borderFocused,\n borderError: borderError ?? this.borderError,\n borderSuccess: borderSuccess ?? this.borderSuccess,\n borderDisabled: borderDisabled ?? this.borderDisabled,\n defaultColor: defaultColor ?? this.defaultColor,\n disabledColor: disabledColor ?? this.disabledColor,\n );\n }\n\n @override\n ThemeExtension\u003CAppInputTheme\u003E lerp(\n covariant ThemeExtension\u003CAppInputTheme\u003E? other,\n double t,\n ) {\n if (other is! AppInputTheme) {\n return this;\n }\n\n return AppInputTheme(\n defaultText: Color.lerp(defaultText, other.defaultText, t)!,\n focusedOnBrand: Color.lerp(focusedOnBrand, other.focusedOnBrand, t)!,\n focusedTextDefault: Color.lerp(\n focusedTextDefault,\n other.focusedTextDefault,\n t,\n )!,\n errorTextDefault: Color.lerp(\n errorTextDefault,\n other.errorTextDefault,\n t,\n )!,\n successTextDefault: Color.lerp(\n successTextDefault,\n other.successTextDefault,\n t,\n )!,\n disabledText: Color.lerp(disabledText, other.disabledText, t)!,\n borderDefault: Color.lerp(borderDefault, other.borderDefault, t)!,\n borderHover: Color.lerp(borderHover, other.borderHover, t)!,\n borderFocused: Color.lerp(borderFocused, other.borderFocused, t)!,\n borderError: Color.lerp(borderError, other.borderError, t)!,\n borderSuccess: Color.lerp(borderSuccess, other.borderSuccess, t)!,\n borderDisabled: Color.lerp(borderDisabled, other.borderDisabled, t)!,\n defaultColor: Color.lerp(defaultColor, other.defaultColor, t)!,\n disabledColor: Color.lerp(disabledColor, other.disabledColor, t)!,\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_24":{"__typename":"Paragraph","id":"aeb6a299c0b1_24","name":"043c","type":"P","href":null,"layout":null,"metadata":null,"text":"Moreover, we have an AppTypography class, which collects font sizes as an independent theme.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":21,"end":34,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_25":{"__typename":"Paragraph","id":"aeb6a299c0b1_25","name":"fa49","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_typography}\n\u002F\u002F\u002F Theme class which provides configuration of [TextStyle]\n\u002F\u002F\u002F {@endtemplate}\ninterface class AppTypography extends ThemeExtension\u003CAppTypography\u003E {\n \u002F\u002F\u002F {@macro app_typography}\n AppTypography({\n required this.buttonLarge,\n required this.buttonMedium,\n required this.buttonSmall,\n });\n\n \u002F\u002F\u002F Button Large\n final TextStyle buttonLarge;\n\n \u002F\u002F\u002F Button Medium\n final TextStyle buttonMedium;\n\n \u002F\u002F\u002F Button Small\n final TextStyle buttonSmall;\n\n @override\n ThemeExtension\u003CAppTypography\u003E copyWith({\n TextStyle? buttonLarge,\n TextStyle? buttonMedium,\n TextStyle? buttonSmall,\n }) {\n return AppTypography(\n buttonLarge: buttonLarge ?? this.buttonLarge,\n buttonMedium: buttonMedium ?? this.buttonMedium,\n buttonSmall: buttonSmall ?? this.buttonSmall,\n );\n }\n\n @override\n ThemeExtension\u003CAppTypography\u003E lerp(\n covariant ThemeExtension\u003CAppTypography\u003E? other,\n double t,\n ) {\n if (other is! AppTypography) {\n return this;\n }\n\n return AppTypography(\n buttonLarge: TextStyle.lerp(buttonLarge, other.buttonLarge, t)!,\n buttonMedium: TextStyle.lerp(buttonMedium, other.buttonMedium, t)!,\n buttonSmall: TextStyle.lerp(buttonSmall, other.buttonSmall, t)!,\n );\n }\n}\n\n\u002F\u002F\u002F {@macro app_typography}\nclass AppRegularTypography extends AppTypography {\n \u002F\u002F\u002F {@macro app_typography}\n AppRegularTypography({\n super.buttonLarge = const TextStyle(\n fontSize: 16,\n height: 24 \u002F 16,\n fontWeight: FontWeight.w500,\n ),\n super.buttonMedium = const TextStyle(\n fontSize: 14,\n height: 20 \u002F 14,\n fontWeight: FontWeight.w500,\n ),\n super.buttonSmall = const TextStyle(\n fontSize: 14,\n height: 20 \u002F 14,\n fontWeight: FontWeight.w500,\n ),\n ),\n });\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_26":{"__typename":"Paragraph","id":"aeb6a299c0b1_26","name":"a503","type":"H4","href":null,"layout":null,"metadata":null,"text":"After all… Components","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_27":{"__typename":"Paragraph","id":"aeb6a299c0b1_27","name":"4adb","type":"P","href":null,"layout":null,"metadata":null,"text":"Component development depends on the design given by the designer. In reality, all the things we have developed above depend on. In our case, for instance, text buttons have many versions, such as size, decoration, etc.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*K-wVpB_DLUMduAkignKPCg.png":{"__typename":"ImageMetadata","id":"1*K-wVpB_DLUMduAkignKPCg.png","originalHeight":764,"originalWidth":768,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_28":{"__typename":"Paragraph","id":"aeb6a299c0b1_28","name":"00e9","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*K-wVpB_DLUMduAkignKPCg.png"},"text":"We had many buttons, but I have shared some of them (text buttons)","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_29":{"__typename":"Paragraph","id":"aeb6a299c0b1_29","name":"f33c","type":"P","href":null,"layout":null,"metadata":null,"text":"Therefore, we have created a base class for our text buttons. Here is the code snippet:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_30":{"__typename":"Paragraph","id":"aeb6a299c0b1_30","name":"1349","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_text_button}\n\u002F\u002F\u002F A custom text button widget that adapts to the platform.\n\u002F\u002F\u002F {@endtemplate}\nabstract class AppTextButton extends StatelessWidget {\n \u002F\u002F\u002F {@macro app_text_button}\n const AppTextButton({\n super.key,\n required this.label,\n this.onTap,\n this.leading,\n this.trailing,\n this.appButtonSize = AppButtonSize.medium,\n });\n\n \u002F\u002F\u002F The label for the text button.\n final String label;\n\n \u002F\u002F\u002F The callback function for the text button.\n final VoidCallback? onTap;\n\n \u002F\u002F\u002F The leading icon for the text button.\n final IconBuilder? leading;\n\n \u002F\u002F\u002F The trailing icon for the text button.\n final IconBuilder? trailing;\n\n \u002F\u002F\u002F The size of the text button.\n final AppButtonSize appButtonSize;\n\n \u002F\u002F\u002F The background color for the text button.\n Color backgroundColor(BuildContext context);\n\n \u002F\u002F\u002F The focus color for the text button.\n Color focusColor(BuildContext context);\n\n \u002F\u002F\u002F The hover color for the text button.\n Color hoverColor(BuildContext context);\n\n \u002F\u002F\u002F The disabled color for the text button.\n Color disabledColor(BuildContext context);\n\n \u002F\u002F\u002F The text color for the text button.\n Color textColor(BuildContext context);\n\n \u002F\u002F\u002F The disabled text color for the text button.\n Color disabledTextColor(BuildContext context) {\n return context.buttonTheme.primaryTextDisabled;\n }\n\n \u002F\u002F\u002F The default border for the text button.\n BorderSide defaultBorder(BuildContext context) =\u003E BorderSide.none;\n\n \u002F\u002F\u002F The focused border for the text button.\n BorderSide focusedBorder(BuildContext context) =\u003E BorderSide.none;\n\n \u002F\u002F\u002F The hover border for the text button.\n BorderSide hoverBorder(BuildContext context) =\u003E BorderSide.none;\n\n \u002F\u002F\u002F The disabled border for the text button.\n BorderSide disabledBorder(BuildContext context) =\u003E BorderSide.none;\n\n @override\n Widget build(BuildContext context) {\n final betweenSpace = switch (appButtonSize) {\n AppButtonSize.small ||\n AppButtonSize.xSmall ||\n AppButtonSize.medium =\u003E\n AppSpacing.xs,\n AppButtonSize.large || AppButtonSize.xlarge =\u003E AppSpacing.sm,\n AppButtonSize.xxLarge =\u003E AppSpacing.lg,\n };\n\n final inputTextColor = WidgetStateProperty.resolveWith(\n (states) {\n if (states.contains(WidgetState.disabled)) {\n return disabledTextColor(context);\n }\n\n return textColor(context);\n },\n );\n\n return ElevatedButton(\n style: ButtonStyle(\n elevation: WidgetStateProperty.all(0),\n splashFactory: NoSplash.splashFactory,\n overlayColor: WidgetStateProperty.resolveWith(\n (states) {\n if (states.contains(WidgetState.disabled)) {\n return disabledColor(context);\n }\n\n if (states.contains(WidgetState.hovered)) {\n return hoverColor(context);\n }\n\n if (states.contains(WidgetState.focused)) {\n return focusColor(context);\n }\n\n if (states.contains(WidgetState.pressed)) {\n return focusColor(context);\n }\n\n return backgroundColor(context);\n },\n ),\n shape: WidgetStateProperty.resolveWith(\n (states) {\n const shape = RoundedRectangleBorder(\n borderRadius: BorderRadius.all(AppRadius.md),\n );\n\n if (states.contains(WidgetState.disabled)) {\n return shape.copyWith(side: disabledBorder(context));\n }\n\n if (states.contains(WidgetState.focused)) {\n return shape.copyWith(side: focusedBorder(context));\n }\n\n if (states.contains(WidgetState.hovered)) {\n return shape.copyWith(side: hoverBorder(context));\n }\n\n if (states.contains(WidgetState.pressed)) {\n return shape.copyWith(side: focusedBorder(context));\n }\n\n return shape.copyWith(side: defaultBorder(context));\n },\n ),\n backgroundColor: WidgetStateProperty.resolveWith(\n (states) {\n if (states.contains(WidgetState.disabled)) {\n return disabledColor(context);\n }\n\n if (states.contains(WidgetState.hovered)) {\n return hoverColor(context);\n }\n\n if (states.contains(WidgetState.focused)) {\n return focusColor(context);\n }\n\n if (states.contains(WidgetState.pressed)) {\n return focusColor(context);\n }\n\n return backgroundColor(context);\n },\n ),\n foregroundColor: inputTextColor,\n fixedSize: WidgetStateProperty.all(\n switch (appButtonSize) {\n AppButtonSize.small ||\n AppButtonSize.xSmall =\u003E\n const Size(double.infinity, 36),\n AppButtonSize.medium =\u003E const Size(double.infinity, 40),\n AppButtonSize.large =\u003E const Size(double.infinity, 44),\n AppButtonSize.xlarge =\u003E const Size(double.infinity, 48),\n AppButtonSize.xxLarge =\u003E const Size(double.infinity, 56),\n },\n ),\n padding: WidgetStateProperty.all(\n switch (appButtonSize) {\n AppButtonSize.small ||\n AppButtonSize.xSmall =\u003E\n const EdgeInsets.symmetric(horizontal: 12),\n AppButtonSize.medium =\u003E const EdgeInsets.symmetric(horizontal: 16),\n AppButtonSize.large =\u003E const EdgeInsets.symmetric(horizontal: 16),\n AppButtonSize.xlarge =\u003E const EdgeInsets.symmetric(horizontal: 20),\n AppButtonSize.xxLarge =\u003E const EdgeInsets.symmetric(horizontal: 24),\n },\n ),\n ),\n onPressed: onTap,\n child: Row(\n mainAxisAlignment: MainAxisAlignment.center,\n mainAxisSize: MainAxisSize.min,\n children: [\n if (leading != null) ...[\n leading!(\n onTap != null ? textColor(context) : disabledTextColor(context),\n ),\n SizedBox(width: betweenSpace),\n ],\n Padding(\n padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxs),\n child: Text(\n label,\n style: switch (appButtonSize) {\n AppButtonSize.small ||\n AppButtonSize.xSmall =\u003E\n context.typography.buttonSmall,\n AppButtonSize.medium =\u003E context.typography.buttonMedium,\n AppButtonSize.large =\u003E context.typography.buttonLarge,\n AppButtonSize.xlarge =\u003E context.typography.buttonXLarge,\n AppButtonSize.xxLarge =\u003E context.typography.button2XLarge,\n },\n ),\n ),\n if (trailing != null) ...[\n SizedBox(width: betweenSpace),\n trailing!(\n onTap != null ? textColor(context) : disabledTextColor(context),\n ),\n ],\n ],\n ),\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_31":{"__typename":"Paragraph","id":"aeb6a299c0b1_31","name":"2011","type":"P","href":null,"layout":null,"metadata":null,"text":"AppButtonSize is a simple enum provided by us:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":13,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_32":{"__typename":"Paragraph","id":"aeb6a299c0b1_32","name":"1dee","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F Enum for button sizes\nenum AppButtonSize {\n \u002F\u002F\u002F Extra small button size\n xSmall,\n\n \u002F\u002F\u002F Small button size\n small,\n\n \u002F\u002F\u002F Medium button size\n medium,\n\n \u002F\u002F\u002F Large button size\n large,\n\n \u002F\u002F\u002F Extra large button size\n xlarge,\n\n \u002F\u002F\u002F Extra extra large button size\n xxLarge,\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_33":{"__typename":"Paragraph","id":"aeb6a299c0b1_33","name":"f082","type":"P","href":null,"layout":null,"metadata":null,"text":"IconBuilder is a simple typedef provided by us. In some cases, we can need to change the color of an icon within the inner state of the button (focus, hover, and other state colors can be applied to the color of the icon)","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":12,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_34":{"__typename":"Paragraph","id":"aeb6a299c0b1_34","name":"6c4e","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F A function that builds an icon widget.\ntypedef IconBuilder = Widget Function(Color iconColor);","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_35":{"__typename":"Paragraph","id":"aeb6a299c0b1_35","name":"6357","type":"P","href":null,"layout":null,"metadata":null,"text":"Eventually, with the help of the base AppTextButton class, we can create our child classes. So, our Primary, Secondary, and Outlined text buttons’ codes will be like the following:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":38,"end":51,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_36":{"__typename":"Paragraph","id":"aeb6a299c0b1_36","name":"d001","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template primary_text_button}\n\u002F\u002F\u002F A custom primary text button widget that adapts to the platform.\n\u002F\u002F\u002F {@endtemplate}\nclass PrimaryTextButton extends AppTextButton {\n \u002F\u002F\u002F {@macro primary_text_button}\n const PrimaryTextButton({\n super.key,\n required super.label,\n super.onTap,\n super.leading,\n super.trailing,\n super.appButtonSize,\n });\n\n @override\n Color backgroundColor(BuildContext context) {\n return context.buttonTheme.primaryDefault;\n }\n\n @override\n Color disabledColor(BuildContext context) {\n return context.buttonTheme.primaryDisabled;\n }\n\n @override\n Color focusColor(BuildContext context) {\n return context.buttonTheme.primaryFocused;\n }\n\n @override\n Color hoverColor(BuildContext context) {\n return context.buttonTheme.primaryHover;\n }\n\n @override\n Color textColor(BuildContext context) {\n return context.buttonTheme.primaryText;\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_37":{"__typename":"Paragraph","id":"aeb6a299c0b1_37","name":"6389","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template secondary_text_button}\n\u002F\u002F\u002F A custom secondary text button widget that adapts to the platform.\n\u002F\u002F\u002F {@endtemplate}\n\u002F\u002F\u002F\nclass SecondaryTextButton extends AppTextButton {\n \u002F\u002F\u002F {@macro secondary_text_button}\n const SecondaryTextButton({\n super.key,\n required super.label,\n super.onTap,\n super.leading,\n super.trailing,\n super.appButtonSize,\n });\n\n @override\n Color backgroundColor(BuildContext context) {\n return context.buttonTheme.secondaryDefault;\n }\n\n @override\n Color disabledColor(BuildContext context) {\n return context.buttonTheme.secondaryDisabled;\n }\n\n @override\n Color focusColor(BuildContext context) {\n return context.buttonTheme.secondaryFocused;\n }\n\n @override\n Color hoverColor(BuildContext context) {\n return context.buttonTheme.secondaryHover;\n }\n\n @override\n Color textColor(BuildContext context) {\n return context.buttonTheme.primaryTextOnBrand;\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_38":{"__typename":"Paragraph","id":"aeb6a299c0b1_38","name":"0ab3","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template outline_text_button}\n\u002F\u002F\u002F A custom outline text button widget that adapts to the platform.\n\u002F\u002F\u002F {@endtemplate}\nclass OutlineTextButton extends AppTextButton {\n \u002F\u002F\u002F {@macro outline_text_button}\n const OutlineTextButton({\n super.key,\n required super.label,\n super.onTap,\n super.leading,\n super.trailing,\n super.appButtonSize,\n });\n\n @override\n Color backgroundColor(BuildContext context) {\n return context.buttonTheme.outlinedDefault;\n }\n\n @override\n Color disabledColor(BuildContext context) {\n return context.buttonTheme.outlinedDisabled;\n }\n\n @override\n Color focusColor(BuildContext context) {\n return context.buttonTheme.outlinedFocused;\n }\n\n @override\n Color hoverColor(BuildContext context) {\n return context.buttonTheme.outlinedHover;\n }\n\n @override\n Color textColor(BuildContext context) {\n return context.buttonTheme.buttonLineDefault;\n }\n\n @override\n BorderSide defaultBorder(BuildContext context) {\n return BorderSide(\n color: context.buttonTheme.buttonLineDefault,\n );\n }\n\n @override\n BorderSide focusedBorder(BuildContext context) {\n return BorderSide(\n color: context.buttonTheme.buttonLineDefault,\n );\n }\n\n @override\n BorderSide hoverBorder(BuildContext context) {\n return BorderSide(\n color: context.buttonTheme.buttonLineDefault,\n );\n }\n\n @override\n BorderSide disabledBorder(BuildContext context) {\n return BorderSide(\n color: context.buttonTheme.outlinedBorderDisabled,\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_39":{"__typename":"Paragraph","id":"aeb6a299c0b1_39","name":"189e","type":"P","href":null,"layout":null,"metadata":null,"text":"For the text field, we have created again an independent AppTextField class.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*7YRH1f5uCDNfVnlDO-QcJg.png":{"__typename":"ImageMetadata","id":"1*7YRH1f5uCDNfVnlDO-QcJg.png","originalHeight":882,"originalWidth":1194,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_40":{"__typename":"Paragraph","id":"aeb6a299c0b1_40","name":"9ec6","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*7YRH1f5uCDNfVnlDO-QcJg.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_41":{"__typename":"Paragraph","id":"aeb6a299c0b1_41","name":"0772","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_text_field}\n\u002F\u002F\u002F A customizable text field widget with various customization options.\n\u002F\u002F\u002F {@endtemplate}\nclass AppTextField extends StatelessWidget {\n \u002F\u002F\u002F {@macro app_text_field}\n const AppTextField({\n super.key,\n this.controller,\n this.labelText,\n this.enabled = true,\n this.obscureText = false,\n this.onChanged,\n this.autovalidateMode = AutovalidateMode.onUserInteraction,\n this.validator,\n this.helperText,\n this.errorText,\n this.suffixIcon,\n this.suffixIconConstraints =\n const BoxConstraints(minHeight: 24, minWidth: 40),\n this.prefixIcon,\n this.prefixIconConstraints =\n const BoxConstraints(minHeight: 24, minWidth: 40),\n this.autofillHints,\n this.onEditingComplete,\n this.inputFormatters,\n this.keyboardType,\n this.maxLines = 1,\n });\n\n \u002F\u002F\u002F The controller for the text field.\n final TextEditingController? controller;\n\n \u002F\u002F\u002F The label text for the text field.\n final String? labelText;\n\n \u002F\u002F\u002F Whether the text field is enabled.\n final bool enabled;\n\n \u002F\u002F\u002F Whether the text field is obscured.\n final bool obscureText;\n\n \u002F\u002F\u002F Called when the text field value changes.\n final ValueChanged\u003CString\u003E? onChanged;\n\n \u002F\u002F\u002F The autovalidate mode for the text field.\n final AutovalidateMode autovalidateMode;\n\n \u002F\u002F\u002F The validator for the text field.\n final FormFieldValidator\u003CString\u003E? validator;\n\n \u002F\u002F\u002F The helper text for the text field.\n final String? helperText;\n\n \u002F\u002F\u002F The error text for the text field.\n final String? errorText;\n\n \u002F\u002F\u002F The suffix icon for the text field.\n final Widget? suffixIcon;\n\n \u002F\u002F\u002F The constraints for the suffix icon.\n final BoxConstraints? suffixIconConstraints;\n\n \u002F\u002F\u002F The prefix icon for the text field.\n final Widget? prefixIcon;\n\n \u002F\u002F\u002F The constraints for the prefix icon.\n final BoxConstraints? prefixIconConstraints;\n\n \u002F\u002F\u002F The autofillhints for app text field.\n final Iterable\u003CString\u003E? autofillHints;\n\n \u002F\u002F\u002F Called when the text field value completed.\n final VoidCallback? onEditingComplete;\n\n \u002F\u002F\u002F The input formatters for the text field.\n final List\u003CTextInputFormatter\u003E? inputFormatters;\n\n \u002F\u002F\u002F The keyboard type for the text field.\n final TextInputType? keyboardType;\n\n \u002F\u002F\u002F the maximum lines available in text field.\n final int maxLines;\n\n @override\n Widget build(BuildContext context) {\n return TextFormField(\n keyboardType: keyboardType,\n inputFormatters: inputFormatters,\n onEditingComplete: onEditingComplete,\n autofillHints: autofillHints,\n controller: controller,\n enabled: enabled,\n obscureText: obscureText,\n onChanged: onChanged,\n autovalidateMode: autovalidateMode,\n validator: validator,\n maxLines: maxLines,\n style: WidgetStateTextStyle.resolveWith(\n (states) {\n late final Color textColor;\n\n if (states.contains(WidgetState.error)) {\n textColor = context.inputTheme.focusedTextDefault;\n } else if (states.contains(WidgetState.focused)) {\n textColor = context.inputTheme.focusedTextDefault;\n } else if (states.contains(WidgetState.disabled)) {\n textColor = context.inputTheme.disabledText;\n } else {\n textColor = context.inputTheme.defaultText;\n }\n\n return context.typography.inputPlaceHolder.copyWith(\n color: textColor,\n );\n },\n ),\n cursorColor: context.inputTheme.focusedTextDefault,\n cursorHeight: 16,\n decoration: InputDecoration(\n labelText: labelText,\n labelStyle: WidgetStateTextStyle.resolveWith(\n (states) {\n late final Color textColor;\n\n if (states.contains(WidgetState.error)) {\n textColor = context.inputTheme.errorTextDefault;\n } else if (states.contains(WidgetState.focused)) {\n textColor = context.inputTheme.focusedOnBrand;\n } else if (states.contains(WidgetState.disabled)) {\n textColor = context.inputTheme.disabledText;\n } else {\n textColor = context.inputTheme.defaultText;\n }\n\n return context.typography.inputPlaceHolder.copyWith(\n color: textColor,\n );\n },\n ),\n floatingLabelStyle: WidgetStateTextStyle.resolveWith(\n (states) {\n late final Color textColor;\n\n if (states.contains(WidgetState.error)) {\n textColor = context.inputTheme.errorTextDefault;\n } else if (states.contains(WidgetState.focused)) {\n textColor = context.inputTheme.focusedOnBrand;\n } else {\n textColor = context.inputTheme.defaultText;\n }\n\n return context.typography.inputLabel.copyWith(\n color: textColor,\n );\n },\n ),\n filled: true,\n fillColor: enabled\n ? context.inputTheme.defaultColor\n : context.inputTheme.disabledColor,\n border: MaterialStateOutlineInputBorder.resolveWith(\n (states) {\n late final Color borderColor;\n\n if (states.contains(WidgetState.error)) {\n borderColor = context.inputTheme.borderError;\n } else if (states.contains(WidgetState.focused)) {\n borderColor = context.inputTheme.borderFocused;\n } else if (states.contains(WidgetState.disabled)) {\n borderColor = context.inputTheme.borderDisabled;\n } else if (states.contains(WidgetState.hovered)) {\n borderColor = context.inputTheme.borderHover;\n } else {\n borderColor = context.inputTheme.borderDefault;\n }\n\n return OutlineInputBorder(\n borderRadius: const BorderRadius.all(AppRadius.md),\n borderSide: BorderSide(\n color: borderColor,\n ),\n );\n },\n ),\n hoverColor: Colors.transparent,\n focusColor: Colors.transparent,\n helperText: helperText,\n helperStyle: WidgetStateTextStyle.resolveWith(\n (states) {\n late final Color textColor;\n\n if (states.contains(WidgetState.error)) {\n textColor = context.inputTheme.errorTextDefault;\n } else if (states.contains(WidgetState.focused)) {\n textColor = context.inputTheme.focusedOnBrand;\n } else if (states.contains(WidgetState.disabled)) {\n textColor = context.inputTheme.disabledText;\n } else {\n textColor = context.inputTheme.defaultText;\n }\n\n return context.typography.inputHint.copyWith(\n color: textColor,\n );\n },\n ),\n errorText: errorText,\n errorStyle: context.typography.inputHint.copyWith(\n color: context.inputTheme.errorTextDefault,\n ),\n suffixIcon: suffixIcon,\n prefixIcon: prefixIcon,\n suffixIconConstraints: suffixIconConstraints,\n prefixIconConstraints: prefixIconConstraints,\n ),\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_42":{"__typename":"Paragraph","id":"aeb6a299c0b1_42","name":"c8c5","type":"H4","href":null,"layout":null,"metadata":null,"text":"Extension to get themes with context","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_43":{"__typename":"Paragraph","id":"aeb6a299c0b1_43","name":"c58e","type":"P","href":null,"layout":null,"metadata":null,"text":"To get themes with context, we have created a simple extension class:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_44":{"__typename":"Paragraph","id":"aeb6a299c0b1_44","name":"9fc2","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F An extension on [BuildContext] that provides access to the current theme.\nextension ThemeExt on BuildContext {\n \u002F\u002F\u002F The current theme.\n ThemeData get theme =\u003E Theme.of(this);\n\n \u002F\u002F\u002Fthe current button theme\n AppButtonTheme get buttonTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appButtonTheme as AppButtonTheme;\n\n \u002F\u002F\u002F The current app checkboxTheme.\n AppCheckboxTheme get checkboxTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appCheckboxTheme as AppCheckboxTheme;\n\n \u002F\u002F\u002F The current app iconTheme.\n AppIconTheme get iconTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appIconTheme as AppIconTheme;\n\n \u002F\u002F\u002F The current app inputTheme.\n AppInputTheme get inputTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appInputTheme as AppInputTheme;\n\n \u002F\u002F\u002F The current app radioTheme.\n AppRadioTheme get radioTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appRadioTheme as AppRadioTheme;\n\n \u002F\u002F\u002F The current app toggleTheme.\n AppToggleTheme get toggleTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appToggleTheme as AppToggleTheme;\n\n \u002F\u002F\u002F The current app typographyTheme.\n AppTypographyTheme get typographyTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appTypographyTheme as AppTypographyTheme;\n\n \u002F\u002F\u002F The current app avatarTheme.\n AppAvatarTheme get avatarTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appAvatarTheme as AppAvatarTheme;\n\n \u002F\u002F\u002F The current app typography.\n AppTypography get typography =\u003E\n theme.extension\u003CAppTheme\u003E()!.appTypography as AppTypography;\n\n \u002F\u002F\u002F The current app navigationTheme.\n AppNavigationTheme get navigationTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appNavigationTheme as AppNavigationTheme;\n\n \u002F\u002F\u002F The current app layoutTheme.\n AppLayoutTheme get layoutTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appLayoutTheme as AppLayoutTheme;\n\n \u002F\u002F\u002F The current app badgeTheme.\n AppBadgeTheme get badgeTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appBadgeTheme as AppBadgeTheme;\n\n \u002F\u002F\u002F The current app breadcrumbTheme.\n AppBreadCrumbTheme get appBreadCrumbTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appBreadCrumbTheme as AppBreadCrumbTheme;\n\n \u002F\u002F\u002F The current app appDropdownTheme.\n AppDropdownTheme get appDropdownTheme =\u003E\n theme.extension\u003CAppTheme\u003E()!.appDropdownTheme as AppDropdownTheme;\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_45":{"__typename":"Paragraph","id":"aeb6a299c0b1_45","name":"2823","type":"H4","href":null,"layout":null,"metadata":null,"text":"So… What is AppTheme there?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_46":{"__typename":"Paragraph","id":"aeb6a299c0b1_46","name":"3c24","type":"P","href":null,"layout":null,"metadata":null,"text":"AppTheme is a simple, immutable class (ThemeExtension) that provides all themes to our application.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":39,"end":53,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_47":{"__typename":"Paragraph","id":"aeb6a299c0b1_47","name":"200b","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_theme}\n\u002F\u002F\u002F Configuration class which collects all Themes of app together and provides\n\u002F\u002F\u002F them as a single instance\n\u002F\u002F\u002F {@endtemplate}\nclass AppTheme extends ThemeExtension\u003CAppTheme\u003E {\n \u002F\u002F\u002F {@macro app_theme}\n const AppTheme({\n required this.appButtonTheme,\n required this.appInputTheme,\n });\n\n \u002F\u002F\u002F {@macro app_theme}\n factory AppTheme.light() {\n return AppTheme(\n appButtonTheme: AppButtonTheme.light(),\n appInputTheme: AppInputTheme.light(),\n );\n }\n\n \u002F\u002F\u002F [AppButtonTheme] instance provides configuration of buttons\n final ThemeExtension\u003CAppButtonTheme\u003E appButtonTheme;\n\n \u002F\u002F\u002F [AppInputTheme] instance provides configuration of [AppTextField]\n final ThemeExtension\u003CAppInputTheme\u003E appInputTheme;\n\n\n @override\n ThemeExtension\u003CAppTheme\u003E copyWith({\n ThemeExtension\u003CAppButtonTheme\u003E? appButtonTheme,\n ThemeExtension\u003CAppInputTheme\u003E? appInputTheme,\n }) {\n return AppTheme(\n appButtonTheme: appButtonTheme ?? this.appButtonTheme,\n appInputTheme: appInputTheme ?? this.appInputTheme,\n );\n }\n\n @override\n ThemeExtension\u003CAppTheme\u003E lerp(\n covariant ThemeExtension\u003CAppTheme\u003E? other,\n double t,\n ) {\n if (other is! AppTheme) {\n return this;\n }\n\n return AppTheme(\n appButtonTheme: appButtonTheme.lerp(other.appButtonTheme, t),\n appInputTheme: appInputTheme.lerp(other.appInputTheme, t),\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_48":{"__typename":"Paragraph","id":"aeb6a299c0b1_48","name":"cb4a","type":"H4","href":null,"layout":null,"metadata":null,"text":"How will we provide our theme for a new project?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*fWTkV2UN5fDvvjRm.gif":{"__typename":"ImageMetadata","id":"0*fWTkV2UN5fDvvjRm.gif","originalHeight":268,"originalWidth":480,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_49":{"__typename":"Paragraph","id":"aeb6a299c0b1_49","name":"97b2","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*fWTkV2UN5fDvvjRm.gif"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_50":{"__typename":"Paragraph","id":"aeb6a299c0b1_50","name":"503d","type":"P","href":null,"layout":null,"metadata":null,"text":"It is not a difficult process. If you know about InheritedWidget, you can understand this step easily.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":49,"end":64,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_51":{"__typename":"Paragraph","id":"aeb6a299c0b1_51","name":"e195","type":"P","href":null,"layout":null,"metadata":null,"text":"Therefore, we created a simple InheritedWidget called ThemeScope to provide our design system for a new project.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":31,"end":46,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":54,"end":64,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_52":{"__typename":"Paragraph","id":"aeb6a299c0b1_52","name":"1225","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template theme_scope}\n\u002F\u002F\u002F InheritedWidget provides [AppTheme] for app\n\u002F\u002F\u002F {@endtemplate}\nclass ThemeScope extends InheritedWidget {\n \u002F\u002F\u002F {@macro theme_scope}\n const ThemeScope({\n super.key,\n required Widget child,\n required this.themeMode,\n required this.appTheme,\n }) : super(child: child);\n\n \u002F\u002F\u002F The current theme mode.\n final ThemeMode themeMode;\n\n \u002F\u002F\u002F The current app theme.\n final AppTheme appTheme;\n\n \u002F\u002F\u002F The current theme.\n static ThemeScope of(BuildContext context) {\n final result = context.dependOnInheritedWidgetOfExactType\u003CThemeScope\u003E();\n assert(result != null, 'No ThemeScope found in context');\n return result!;\n }\n\n @override\n bool updateShouldNotify(ThemeScope oldWidget) =\u003E true;\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_53":{"__typename":"Paragraph","id":"aeb6a299c0b1_53","name":"7b47","type":"H4","href":null,"layout":null,"metadata":null,"text":"ThemeMode handler","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_54":{"__typename":"Paragraph","id":"aeb6a299c0b1_54","name":"89e3","type":"P","href":null,"layout":null,"metadata":null,"text":"For handling the dark, light, and system mode switching process, your can write a simple controller and initializer as the following:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_55":{"__typename":"Paragraph","id":"aeb6a299c0b1_55","name":"7f8b","type":"PRE","href":null,"layout":null,"metadata":null,"text":"const _kThemeMode = 'themeMode';\n\n\u002F\u002F\u002F {@template theme_scope_widget}\n\u002F\u002F\u002F A class which handles all theme processes\n\u002F\u002F\u002F\n\u002F\u002F\u002F initialize() method should be used as app starter in order to use\n\u002F\u002F\u002F [AppTheme] in the app\n\n\u002F\u002F\u002F {@endtemplate}\nclass ThemeScopeWidget extends StatefulWidget {\n \u002F\u002F\u002F {@macro theme_scope_widget}\n const ThemeScopeWidget({\n super.key,\n required this.child,\n required this.preferences,\n });\n\n \u002F\u002F\u002F The child widget\n final Widget child;\n\n \u002F\u002F\u002F The shared preferences\n final SharedPreferences preferences;\n\n \u002F\u002F\u002F Initialize the [ThemeScopeWidget] with the given [child] widget\n static Future\u003CThemeScopeWidget\u003E initialize(Widget child) async {\n final preferences = await SharedPreferences.getInstance();\n return ThemeScopeWidget(\n preferences: preferences,\n child: child,\n );\n }\n\n \u002F\u002F\u002F In order to use methods of [ThemeScopeWidget] this function\n \u002F\u002F\u002F should be called first. Theme change process will handled by\n \u002F\u002F\u002F [ThemeScopeWidget] automatically.\n static ThemeScopeWidgetState? of(BuildContext context) {\n return context.findRootAncestorStateOfType\u003CThemeScopeWidgetState\u003E();\n }\n\n @override\n State\u003CThemeScopeWidget\u003E createState() =\u003E ThemeScopeWidgetState();\n}\n\n\u002F\u002F\u002F The state for [ThemeScopeWidget].\nclass ThemeScopeWidgetState extends State\u003CThemeScopeWidget\u003E {\n ThemeMode? _themeMode;\n\n \u002F\u002F\u002F Change the theme mode\n Future\u003Cvoid\u003E changeTo(ThemeMode themeMode) async {\n if (_themeMode == themeMode) return;\n\n try {\n final index = ThemeMode.values.indexOf(themeMode);\n await widget.preferences.setInt(_kThemeMode, index);\n\n setState(() {\n _themeMode = themeMode;\n });\n } on Exception catch (_) {}\n }\n\n @override\n void didChangeDependencies() {\n super.didChangeDependencies();\n\n try {\n final themeModeIndex = widget.preferences.getInt(_kThemeMode) ?? 0;\n final themeMode = ThemeMode.values[themeModeIndex];\n\n _themeMode = themeMode;\n } on Exception catch (_) {\n _themeMode = ThemeMode.system;\n }\n }\n\n @override\n Widget build(BuildContext context) {\n final brightness = MediaQuery.platformBrightnessOf(context);\n\n final appTheme = switch (_themeMode!) {\n ThemeMode.light =\u003E AppTheme.light(),\n ThemeMode.dark =\u003E AppTheme.light(),\n ThemeMode.system =\u003E\n brightness == Brightness.dark ? AppTheme.light() : AppTheme.light(),\n };\n\n return ThemeScope(\n themeMode: _themeMode!,\n appTheme: appTheme,\n child: widget.child,\n );\n }\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_56":{"__typename":"Paragraph","id":"aeb6a299c0b1_56","name":"70d1","type":"BQ","href":null,"layout":null,"metadata":null,"text":"You can call ThemeScopeWidget.of(context).changeTo function to change your ThemeMode in anywhere!","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":13,"end":50,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_57":{"__typename":"Paragraph","id":"aeb6a299c0b1_57","name":"aa4f","type":"H4","href":null,"layout":null,"metadata":null,"text":"The last step is to initialize your project with our Design System","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_58":{"__typename":"Paragraph","id":"aeb6a299c0b1_58","name":"8673","type":"P","href":null,"layout":null,"metadata":null,"text":"We wrote this design system implementation as an independent package and used it in different apps.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_59":{"__typename":"Paragraph","id":"aeb6a299c0b1_59","name":"41f5","type":"P","href":null,"layout":null,"metadata":null,"text":"Firstly, we should initialize our wrapper:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_60":{"__typename":"Paragraph","id":"aeb6a299c0b1_60","name":"90a0","type":"PRE","href":null,"layout":null,"metadata":null,"text":"void main() async {\n WidgetsFlutterBinding.ensureInitialized();\n final app = await ThemeScopeWidget.initialize(const MyApp());\n runApp(app);\n }","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_61":{"__typename":"Paragraph","id":"aeb6a299c0b1_61","name":"e8fa","type":"P","href":null,"layout":null,"metadata":null,"text":"We have another option to provide:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_62":{"__typename":"Paragraph","id":"aeb6a299c0b1_62","name":"2c7f","type":"PRE","href":null,"layout":null,"metadata":null,"text":"void main() async {\n WidgetsFlutterBinding.ensureInitialized();\n final preferences = await SharedPreferences.getInstance();\n\n runApp(\n ThemeScopeWidget(\n preferences: preferences,\n child: const MyApp(),\n ),\n );\n }","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_63":{"__typename":"Paragraph","id":"aeb6a299c0b1_63","name":"6502","type":"P","href":null,"layout":null,"metadata":null,"text":"And, in the MaterialApp, it should be registered as an extension:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_64":{"__typename":"Paragraph","id":"aeb6a299c0b1_64","name":"af6e","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final theme = ThemeScope.of(context);\n\nreturn MaterialApp(\n title: 'Flutter App',\n themeMode: theme.themeMode,\n theme: ThemeData(extensions: [theme.appTheme]),\n darkTheme: ThemeData(extensions: [theme.appTheme]),\n home: const MyHomePage(title: 'Flutter Demo Home Page'),\n);","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_65":{"__typename":"Paragraph","id":"aeb6a299c0b1_65","name":"0ed6","type":"P","href":null,"layout":null,"metadata":null,"text":"There are a few extension methods that allow developers to access any theme, typography, and just a few lines of code.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_66":{"__typename":"Paragraph","id":"aeb6a299c0b1_66","name":"95bf","type":"PRE","href":null,"layout":null,"metadata":null,"text":"context.buttonTheme.linkHover;\ncontext.checkboxTheme.disabled;\ncontext.typography.titleSmall;","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_67":{"__typename":"Paragraph","id":"aeb6a299c0b1_67","name":"dceb","type":"P","href":null,"layout":null,"metadata":null,"text":"To change the theme of app, just following function should be called:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_68":{"__typename":"Paragraph","id":"aeb6a299c0b1_68","name":"2e75","type":"PRE","href":null,"layout":null,"metadata":null,"text":"final themeScope = ThemeScopeWidget.of(context);\nthemeScope.changeTo(ThemeMode.light);","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_69":{"__typename":"Paragraph","id":"aeb6a299c0b1_69","name":"9398","type":"H4","href":null,"layout":null,"metadata":null,"text":"What is about Assets?","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_70":{"__typename":"Paragraph","id":"aeb6a299c0b1_70","name":"77d9","type":"P","href":null,"layout":null,"metadata":null,"text":"In general, assets are the part of the design system. Therefore, we created an independent package that stores and emits PNGs, SVGs, etc, for any project as a package.","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*PTRxNMJocD1modj5OucZzg.png":{"__typename":"ImageMetadata","id":"1*PTRxNMJocD1modj5OucZzg.png","originalHeight":632,"originalWidth":716,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_71":{"__typename":"Paragraph","id":"aeb6a299c0b1_71","name":"8808","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*PTRxNMJocD1modj5OucZzg.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_72":{"__typename":"Paragraph","id":"aeb6a299c0b1_72","name":"bc8f","type":"P","href":null,"layout":null,"metadata":null,"text":"So, as you can see, the assets package stores fonts, raster, and vector images.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":24,"end":30,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_73":{"__typename":"Paragraph","id":"aeb6a299c0b1_73","name":"2083","type":"ULI","href":null,"layout":null,"metadata":null,"text":"rasters mean PNGs, JPEGs, etc","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":7,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_74":{"__typename":"Paragraph","id":"aeb6a299c0b1_74","name":"9849","type":"ULI","href":null,"layout":null,"metadata":null,"text":"vectors mean SVGs","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":7,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_75":{"__typename":"Paragraph","id":"aeb6a299c0b1_75","name":"2043","type":"P","href":null,"layout":null,"metadata":null,"text":"Why did we divide them in the different package?","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":0,"end":48,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"EM","start":0,"end":48,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_76":{"__typename":"Paragraph","id":"aeb6a299c0b1_76","name":"ae7b","type":"P","href":null,"layout":null,"metadata":null,"text":"To optimize SVGs, we have used the new way, for all SVGs in the vectors package will be compiled to optimized version at the build time:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:1*vtmDBwLpn8o8WinXnn6UZA.png":{"__typename":"ImageMetadata","id":"1*vtmDBwLpn8o8WinXnn6UZA.png","originalHeight":638,"originalWidth":1840,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_77":{"__typename":"Paragraph","id":"aeb6a299c0b1_77","name":"8d5e","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:1*vtmDBwLpn8o8WinXnn6UZA.png"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_78":{"__typename":"Paragraph","id":"aeb6a299c0b1_78","name":"6963","type":"P","href":null,"layout":null,"metadata":null,"text":"And, we need SSOT (Single Source of Truth) to get our assets:","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_79":{"__typename":"Paragraph","id":"aeb6a299c0b1_79","name":"b8ff","type":"PRE","href":null,"layout":null,"metadata":null,"text":"\u002F\u002F\u002F {@template app_icons}\n\u002F\u002F\u002F The [AppAssets] class contains all the icons used in the app.\n\u002F\u002F\u002F {@endtemplate}\nabstract class AppAssets {\n \u002F\u002F\u002F person icon\n static const person = AssetBytesLoader(\n 'vectors\u002Fperson.svg',\n packageName: 'assets',\n );\n\n \u002F\u002F\u002F left arrow icon\n static const leftArrow = AssetBytesLoader(\n 'vectors\u002Fleft_icon.svg',\n packageName: 'assets',\n );\n\n static const sittingSad = 'rasters\u002Fsitting_sad.png';\n static const magnifyingGlass = 'rasters\u002Fmagnifying_glass.png';\n}","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_80":{"__typename":"Paragraph","id":"aeb6a299c0b1_80","name":"477e","type":"BQ","href":null,"layout":null,"metadata":null,"text":"You can divide raster and vector graphics class too","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_81":{"__typename":"Paragraph","id":"aeb6a299c0b1_81","name":"7fb1","type":"P","href":null,"layout":null,"metadata":null,"text":"Keep in mind that, when you use raster graphic in any project as package (you defined asset package in pubspec and use it), you should define package field:","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"STRONG","start":86,"end":91,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":142,"end":149,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_82":{"__typename":"Paragraph","id":"aeb6a299c0b1_82","name":"e2b8","type":"PRE","href":null,"layout":null,"metadata":null,"text":"Image.asset(AppAssets.sittingSad, package: 'assets')","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":{"__typename":"CodeBlockMetadata","mode":"EXPLICIT","lang":"dart"},"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_83":{"__typename":"Paragraph","id":"aeb6a299c0b1_83","name":"5d92","type":"P","href":null,"layout":null,"metadata":null,"text":"And, if you want to export font from other package you should define your fonts inside lib folder. For more information, you can check the Export fonts from a package article from Flutter documentation.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":139,"end":166,"href":"https:\u002F\u002Fdocs.flutter.dev\u002Fcookbook\u002Fdesign\u002Fpackage-fonts","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":27,"end":31,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":87,"end":90,"href":null,"anchorType":null,"userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":139,"end":166,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_84":{"__typename":"Paragraph","id":"aeb6a299c0b1_84","name":"a2a9","type":"H4","href":null,"layout":null,"metadata":null,"text":"Testing your design system as an independent app","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_85":{"__typename":"Paragraph","id":"aeb6a299c0b1_85","name":"e14c","type":"P","href":null,"layout":null,"metadata":null,"text":"For this purpose, we are using Widgetbook. It is a huge topic to write about. In order not to prolong the article further, I did not write about this topic in more detail. It has a great documentation, you can check it.","hasDropCap":null,"dropCapImage":null,"markups":[{"__typename":"Markup","type":"A","start":31,"end":41,"href":"https:\u002F\u002Fdocs.widgetbook.io\u002F","anchorType":"LINK","userId":null,"linkMetadata":null},{"__typename":"Markup","type":"STRONG","start":31,"end":41,"href":null,"anchorType":null,"userId":null,"linkMetadata":null}],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"Paragraph:aeb6a299c0b1_86":{"__typename":"Paragraph","id":"aeb6a299c0b1_86","name":"5c17","type":"P","href":null,"layout":null,"metadata":null,"text":"That is it. If you like my article, don’t forget to clap!","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"ImageMetadata:0*9A8DvdJQnbvdwQsB.gif":{"__typename":"ImageMetadata","id":"0*9A8DvdJQnbvdwQsB.gif","originalHeight":336,"originalWidth":504,"focusPercentX":null,"focusPercentY":null,"alt":null},"Paragraph:aeb6a299c0b1_87":{"__typename":"Paragraph","id":"aeb6a299c0b1_87","name":"e045","type":"IMG","href":null,"layout":"INSET_CENTER","metadata":{"__ref":"ImageMetadata:0*9A8DvdJQnbvdwQsB.gif"},"text":"","hasDropCap":null,"dropCapImage":null,"markups":[],"codeBlockMetadata":null,"iframe":null,"mixtapeMetadata":null},"CollectionViewerEdge:collectionId:86fb29d7cc6a-viewerId:lo_9a13a25784b9":{"__typename":"CollectionViewerEdge","id":"collectionId:86fb29d7cc6a-viewerId:lo_9a13a25784b9","isEditor":false,"isMuting":false},"ImageMetadata:1*4JWvD8Buzbf7cA6EfRvkiA.png":{"__typename":"ImageMetadata","id":"1*4JWvD8Buzbf7cA6EfRvkiA.png","originalWidth":375,"originalHeight":54},"PostViewerEdge:postId:bc2aebb8bb02-viewerId:lo_9a13a25784b9":{"__typename":"PostViewerEdge","shouldIndexPostForExternalSearch":true,"id":"postId:bc2aebb8bb02-viewerId:lo_9a13a25784b9"},"Tag:flutter":{"__typename":"Tag","id":"flutter","displayTitle":"Flutter","normalizedTagSlug":"flutter"},"Tag:design-system":{"__typename":"Tag","id":"design-system","displayTitle":"Design System","normalizedTagSlug":"design-systems"},"Tag:inherited-widget":{"__typename":"Tag","id":"inherited-widget","displayTitle":"Inherited Widget","normalizedTagSlug":"inheritedwidget"},"Tag:assets-management":{"__typename":"Tag","id":"assets-management","displayTitle":"Assets Management","normalizedTagSlug":"assets-management"},"Tag:components":{"__typename":"Tag","id":"components","displayTitle":"Components","normalizedTagSlug":"components"},"Post:bc2aebb8bb02":{"__typename":"Post","id":"bc2aebb8bb02","collection":{"__ref":"Collection:86fb29d7cc6a"},"content({\"postMeteringOptions\":{}})":{"__typename":"PostContent","isLockedPreviewOnly":false,"bodyModel":{"__typename":"RichText","sections":[{"__typename":"Section","name":"4313","startIndex":0,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"b5c3","startIndex":1,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"f6af","startIndex":3,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"dfa6","startIndex":5,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"4565","startIndex":15,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"0d41","startIndex":17,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"99c6","startIndex":26,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"3654","startIndex":42,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"75c6","startIndex":45,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"420b","startIndex":48,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"ff0b","startIndex":53,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"b721","startIndex":57,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"c8fe","startIndex":69,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"5f7c","startIndex":84,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null},{"__typename":"Section","name":"ef8e","startIndex":86,"textLayout":null,"imageLayout":null,"backgroundImage":null,"videoLayout":null,"backgroundVideo":null}],"paragraphs":[{"__ref":"Paragraph:aeb6a299c0b1_0"},{"__ref":"Paragraph:aeb6a299c0b1_1"},{"__ref":"Paragraph:aeb6a299c0b1_2"},{"__ref":"Paragraph:aeb6a299c0b1_3"},{"__ref":"Paragraph:aeb6a299c0b1_4"},{"__ref":"Paragraph:aeb6a299c0b1_5"},{"__ref":"Paragraph:aeb6a299c0b1_6"},{"__ref":"Paragraph:aeb6a299c0b1_7"},{"__ref":"Paragraph:aeb6a299c0b1_8"},{"__ref":"Paragraph:aeb6a299c0b1_9"},{"__ref":"Paragraph:aeb6a299c0b1_10"},{"__ref":"Paragraph:aeb6a299c0b1_11"},{"__ref":"Paragraph:aeb6a299c0b1_12"},{"__ref":"Paragraph:aeb6a299c0b1_13"},{"__ref":"Paragraph:aeb6a299c0b1_14"},{"__ref":"Paragraph:aeb6a299c0b1_15"},{"__ref":"Paragraph:aeb6a299c0b1_16"},{"__ref":"Paragraph:aeb6a299c0b1_17"},{"__ref":"Paragraph:aeb6a299c0b1_18"},{"__ref":"Paragraph:aeb6a299c0b1_19"},{"__ref":"Paragraph:aeb6a299c0b1_20"},{"__ref":"Paragraph:aeb6a299c0b1_21"},{"__ref":"Paragraph:aeb6a299c0b1_22"},{"__ref":"Paragraph:aeb6a299c0b1_23"},{"__ref":"Paragraph:aeb6a299c0b1_24"},{"__ref":"Paragraph:aeb6a299c0b1_25"},{"__ref":"Paragraph:aeb6a299c0b1_26"},{"__ref":"Paragraph:aeb6a299c0b1_27"},{"__ref":"Paragraph:aeb6a299c0b1_28"},{"__ref":"Paragraph:aeb6a299c0b1_29"},{"__ref":"Paragraph:aeb6a299c0b1_30"},{"__ref":"Paragraph:aeb6a299c0b1_31"},{"__ref":"Paragraph:aeb6a299c0b1_32"},{"__ref":"Paragraph:aeb6a299c0b1_33"},{"__ref":"Paragraph:aeb6a299c0b1_34"},{"__ref":"Paragraph:aeb6a299c0b1_35"},{"__ref":"Paragraph:aeb6a299c0b1_36"},{"__ref":"Paragraph:aeb6a299c0b1_37"},{"__ref":"Paragraph:aeb6a299c0b1_38"},{"__ref":"Paragraph:aeb6a299c0b1_39"},{"__ref":"Paragraph:aeb6a299c0b1_40"},{"__ref":"Paragraph:aeb6a299c0b1_41"},{"__ref":"Paragraph:aeb6a299c0b1_42"},{"__ref":"Paragraph:aeb6a299c0b1_43"},{"__ref":"Paragraph:aeb6a299c0b1_44"},{"__ref":"Paragraph:aeb6a299c0b1_45"},{"__ref":"Paragraph:aeb6a299c0b1_46"},{"__ref":"Paragraph:aeb6a299c0b1_47"},{"__ref":"Paragraph:aeb6a299c0b1_48"},{"__ref":"Paragraph:aeb6a299c0b1_49"},{"__ref":"Paragraph:aeb6a299c0b1_50"},{"__ref":"Paragraph:aeb6a299c0b1_51"},{"__ref":"Paragraph:aeb6a299c0b1_52"},{"__ref":"Paragraph:aeb6a299c0b1_53"},{"__ref":"Paragraph:aeb6a299c0b1_54"},{"__ref":"Paragraph:aeb6a299c0b1_55"},{"__ref":"Paragraph:aeb6a299c0b1_56"},{"__ref":"Paragraph:aeb6a299c0b1_57"},{"__ref":"Paragraph:aeb6a299c0b1_58"},{"__ref":"Paragraph:aeb6a299c0b1_59"},{"__ref":"Paragraph:aeb6a299c0b1_60"},{"__ref":"Paragraph:aeb6a299c0b1_61"},{"__ref":"Paragraph:aeb6a299c0b1_62"},{"__ref":"Paragraph:aeb6a299c0b1_63"},{"__ref":"Paragraph:aeb6a299c0b1_64"},{"__ref":"Paragraph:aeb6a299c0b1_65"},{"__ref":"Paragraph:aeb6a299c0b1_66"},{"__ref":"Paragraph:aeb6a299c0b1_67"},{"__ref":"Paragraph:aeb6a299c0b1_68"},{"__ref":"Paragraph:aeb6a299c0b1_69"},{"__ref":"Paragraph:aeb6a299c0b1_70"},{"__ref":"Paragraph:aeb6a299c0b1_71"},{"__ref":"Paragraph:aeb6a299c0b1_72"},{"__ref":"Paragraph:aeb6a299c0b1_73"},{"__ref":"Paragraph:aeb6a299c0b1_74"},{"__ref":"Paragraph:aeb6a299c0b1_75"},{"__ref":"Paragraph:aeb6a299c0b1_76"},{"__ref":"Paragraph:aeb6a299c0b1_77"},{"__ref":"Paragraph:aeb6a299c0b1_78"},{"__ref":"Paragraph:aeb6a299c0b1_79"},{"__ref":"Paragraph:aeb6a299c0b1_80"},{"__ref":"Paragraph:aeb6a299c0b1_81"},{"__ref":"Paragraph:aeb6a299c0b1_82"},{"__ref":"Paragraph:aeb6a299c0b1_83"},{"__ref":"Paragraph:aeb6a299c0b1_84"},{"__ref":"Paragraph:aeb6a299c0b1_85"},{"__ref":"Paragraph:aeb6a299c0b1_86"},{"__ref":"Paragraph:aeb6a299c0b1_87"}]},"validatedShareKey":"","shareKeyCreator":null},"creator":{"__ref":"User:571559932209"},"inResponseToEntityType":null,"isLocked":false,"isMarkedPaywallOnly":false,"lockedSource":"LOCKED_POST_SOURCE_NONE","mediumUrl":"https:\u002F\u002Fmedium.com\u002Fflutter-community\u002Fdesign-system-from-scratch-in-flutter-bc2aebb8bb02","primaryTopic":null,"topics":[],"isPublished":true,"latestPublishedVersion":"aeb6a299c0b1","visibility":"PUBLIC","postResponses":{"__typename":"PostResponses","count":6},"clapCount":801,"allowResponses":true,"isLimitedState":false,"title":"Design System from scratch in Flutter","isSeries":false,"sequence":null,"uniqueSlug":"design-system-from-scratch-in-flutter-bc2aebb8bb02","socialTitle":"","socialDek":"","canonicalUrl":"","metaDescription":"","latestPublishedAt":1730910521568,"readingTime":16.683018867924527,"previewContent":{"__typename":"PreviewContent","subtitle":"Initially, many solutions are available to create a Design System for your app in Flutter. I want to share my experience with Design…"},"previewImage":{"__ref":"ImageMetadata:1*CtlNPhgJx2QqxiDGQ8kNNg.png"},"isShortform":false,"seoTitle":"","firstPublishedAt":1730910521568,"updatedAt":1731542073812,"shortformType":"SHORTFORM_TYPE_LINK","seoDescription":"","viewerEdge":{"__ref":"PostViewerEdge:postId:bc2aebb8bb02-viewerId:lo_9a13a25784b9"},"isSuspended":false,"license":"ALL_RIGHTS_RESERVED","tags":[{"__ref":"Tag:flutter"},{"__ref":"Tag:design-system"},{"__ref":"Tag:inherited-widget"},{"__ref":"Tag:assets-management"},{"__ref":"Tag:components"}],"isNewsletter":false,"statusForCollection":"APPROVED","pendingCollection":null,"detectedLanguage":"en","wordCount":4103,"layerCake":6,"responsesLocked":false}}</script><script>window.__MIDDLEWARE_STATE__={"session":{"xsrf":""},"cache":{"cacheStatus":"HIT"}}</script><script src="https://cdn-client.medium.com/lite/static/js/manifest.b2314f6d.js"></script><script src="https://cdn-client.medium.com/lite/static/js/9865.1496d74a.js"></script><script src="https://cdn-client.medium.com/lite/static/js/main.24534aeb.js"></script><script src="https://cdn-client.medium.com/lite/static/js/instrumentation.d9108df7.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/reporting.ff22a7a5.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9120.5df29668.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5049.d1ead72d.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4810.6318add7.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6618.db187378.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2707.b0942613.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/9977.5b3eb23a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8599.1ab63137.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5250.9f9e01d2.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6349.b071a958.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2648.26563adf.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8393.826a25fb.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7079.67349d50.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3735.afb7e926.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/5642.a2d9f6a1.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6546.cd03f950.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/6834.08de95de.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7346.72622eb9.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2420.2a5e2d95.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/839.ca7937c2.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7975.d195c6f1.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2106.21ff89d3.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/7394.3d049572.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2961.00a48598.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8204.c4082863.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/4391.59acaed3.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostPage.MainContent.c8a11795.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/8414.6565ad5f.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/3974.8d3e0217.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/2527.a0afad8a.chunk.js"></script> <script src="https://cdn-client.medium.com/lite/static/js/PostResponsesContent.36c2ecf4.chunk.js"></script><script>window.main();</script><script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'8e6f9087b8c4ce4a',t:'MTczMjM0ODE4Ny4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body></html>