<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nimus</title>
    <description>The latest articles on DEV Community by Nimus (@nimusoes).</description>
    <link>https://dev.to/nimusoes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3051649%2Fd72a00f2-a329-4342-8096-1a23fa6b54d4.jpeg</url>
      <title>DEV Community: Nimus</title>
      <link>https://dev.to/nimusoes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nimusoes"/>
    <language>en</language>
    <item>
      <title>This is my solution to the Character Counter challenge of FrontendMentor.</title>
      <dc:creator>Nimus</dc:creator>
      <pubDate>Fri, 25 Apr 2025 06:11:49 +0000</pubDate>
      <link>https://dev.to/nimusoes/this-is-my-solution-to-the-character-counter-challenge-of-frontendmentor-3p9b</link>
      <guid>https://dev.to/nimusoes/this-is-my-solution-to-the-character-counter-challenge-of-frontendmentor-3p9b</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8" class="crayons-story__hidden-navigation-link"&gt;Character Counter App | Frontend Project #2&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/nimusoes" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3051649%2Fd72a00f2-a329-4342-8096-1a23fa6b54d4.jpeg" alt="nimusoes profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nimusoes" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Nimus
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Nimus
                
              
              &lt;div id="story-author-preview-content-2420988" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nimusoes" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3051649%2Fd72a00f2-a329-4342-8096-1a23fa6b54d4.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Nimus&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 21 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8" id="article-link-2420988"&gt;
          Character Counter App | Frontend Project #2
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/frontend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;frontend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              1&lt;span class="hidden s:inline"&gt; comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Character Counter App | Frontend Project #2</title>
      <dc:creator>Nimus</dc:creator>
      <pubDate>Mon, 21 Apr 2025 04:40:55 +0000</pubDate>
      <link>https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8</link>
      <guid>https://dev.to/nimusoes/character-counter-app-frontend-project-2-31p8</guid>
      <description>&lt;h1&gt;
  
  
  An interactive React application offering real-time analysis of user input.
&lt;/h1&gt;

&lt;p&gt;‎ &lt;br&gt;
This project is the first React application I built using the concept of &lt;strong&gt;state&lt;/strong&gt;. It provides real-time analysis of user input, including character count, word count, sentence count, estimated reading time, and character density.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes/react-character-counter" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nimus-oes.github.io/react-character-counter/" rel="noopener noreferrer"&gt;Go to Live Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Project Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Users should be able to input text and select checkbox options to receive an analysis of the text&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Live Text Analysis&lt;/strong&gt;: Updates character, word, sentence count, and character density with each keystroke&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Responsive to User Options&lt;/strong&gt;: Reflects checkbox options like excluding spaces and setting character limits in the analysis&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Character Density Visualization&lt;/strong&gt;: Displays density as progress bars for each character&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Immediate Feedback&lt;/strong&gt;: Shows alert messages when the character limit is exceeded&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Dark/Light Mode Toggle&lt;/strong&gt;: Allows users to switch between themes via an icon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Framework: React 19.0.0&lt;/li&gt;
&lt;li&gt;Build Tool: Vite 6.2.0&lt;/li&gt;
&lt;li&gt;Styling: CSS&lt;/li&gt;
&lt;li&gt;Deployment: GitHub Pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Directory Structure
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;src
├─ App.css
├─ App.jsx
├─ assets
├─ components
│  ├─ BannerList/
│  ├─ DensityList/
│  ├─ Header/
│  ├─ Main/
│  ├─ Options/
│  ├─ ReadingTime/
│  └─ TextInput/
├─ context
│  └─ TextContext.jsx
├─ index.css
├─ index.jsx
├─ locales
│  └─ en.json
└─ utils
   └─ formatText.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The components are nested as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; // Logo, theme toggle, 'isDark' state
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; // 'content' state
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; // Input field
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Options&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; // Exclude spaces, character limit checkboxes
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReadingTime&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; // Estimated reading time
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BannerList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; // Character, word, sentence count
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DensityList&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; // Character density
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;‎ &lt;br&gt;
‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  State Management
&lt;/h2&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  State Management Principles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Colocation&lt;/strong&gt;: States are declared in the components where they’re used&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Lifting&lt;/strong&gt;: Shared states are lifted to the nearest common parent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Context&lt;/strong&gt;: Context API was not necessary for state management, as state lifting was sufficient&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal State&lt;/strong&gt;: Only essential states are declared; derived values (e.g. character counts) are calculated from existing state rather than stored separately&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  States in the Project
&lt;/h3&gt;

&lt;p&gt;All states are managed locally using &lt;code&gt;useState&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;isDark&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracks current theme&lt;/li&gt;
&lt;li&gt;Type: Boolean&lt;/li&gt;
&lt;li&gt;Declared in: &lt;code&gt;&amp;lt;Header&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Used in: &lt;code&gt;&amp;lt;Header&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;content&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores user input and option selections&lt;/li&gt;
&lt;li&gt;Type: Object&lt;/li&gt;
&lt;li&gt;Declared in: &lt;code&gt;&amp;lt;Main&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Used in: &lt;code&gt;&amp;lt;Main&amp;gt;&lt;/code&gt; and its children&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;isOpen&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controls accordion toggle state&lt;/li&gt;
&lt;li&gt;Type: Boolean&lt;/li&gt;
&lt;li&gt;Declared in: &lt;code&gt;&amp;lt;DensityList&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Used in: &lt;code&gt;&amp;lt;DensityList&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;‎ ‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Key Feature Implementations
&lt;/h2&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  Real-Time Input Handling
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-input-handling.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-input-handling.png%3Fraw%3Dtrue" width="2538" height="1108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;User input and checkbox values are synced to the &lt;code&gt;content&lt;/code&gt; state using event handlers. The content object includes properties like &lt;code&gt;userinput&lt;/code&gt;, &lt;code&gt;nospace&lt;/code&gt;, &lt;code&gt;limit&lt;/code&gt;, and &lt;code&gt;maxlength&lt;/code&gt;. All inputs are controlled, meaning their values are tied directly to the state and updated dynamically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;User input → Updates &lt;code&gt;content&lt;/code&gt; → Inputs receive &lt;code&gt;value&lt;/code&gt;/&lt;code&gt;checked&lt;/code&gt; from &lt;code&gt;content&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h4&gt;
  
  
  Why Use Controlled Inputs
&lt;/h4&gt;

&lt;p&gt;Using controlled inputs makes sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input values affect other parts of the UI&lt;/li&gt;
&lt;li&gt;You need to validate or modify user input&lt;/li&gt;
&lt;li&gt;Input values can be changed programmatically (e.g. resetting fields)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this project, all inputs are controlled to ensure consistency and allow future scalability—such as trimming characters that exceed the set limit.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  Live Text Analysis Based on User Options
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-live-analysis.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-live-analysis.png%3Fraw%3Dtrue" width="2550" height="1370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every time the user types or toggles an option, the app re-renders and recalculates the results. There’s no separate state for analysis data—everything is computed on the fly using utility functions based on the current &lt;code&gt;content&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Logic for Character, Word, and Sentence Counts
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;noSpaceTotalChars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countWord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wordList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;wordList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countSentence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sentenceList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;.?!&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Split by ".", "?", or "!"&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sentenceList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Logic for Letter Density
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSortedDensity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minThreshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ignoreCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayUpper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;removeSpecialCharsSpaces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;densityCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getDensity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearedText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ignoreCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayUpper&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;densityCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rangedDensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;densityCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;minThreshold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rangedDensity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Returns [['char', count, density], ...]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Rendering Results
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;BannerList&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;DensityList&amp;gt;&lt;/code&gt; components pass the analysis data to their child components (&lt;code&gt;&amp;lt;Banner&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Density&amp;gt;&lt;/code&gt;) and render them as JSX arrays.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  Immediate Feedback When Character Limit is Exceeded
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-limit-exceeds.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FNimus-oes%2Ffrontend-project-collection%2Fblob%2Fmain%2Fassets%2F02-character-counter-limit-exceeds.png%3Fraw%3Dtrue" width="2564" height="1166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user input goes beyond the defined character limit, the input field’s border changes color, and a warning message is displayed.&lt;/p&gt;

&lt;p&gt;This limit check isn’t stored as a separate piece of state—it’s derived directly from the existing &lt;code&gt;content&lt;/code&gt; state on every render. The computed value is stored in the &lt;code&gt;isLimitReached&lt;/code&gt; variable, which is shared between the &lt;code&gt;&amp;lt;Main&amp;gt;&lt;/code&gt; component (for conditionally rendering the alert message) and &lt;code&gt;&amp;lt;TextInput&amp;gt;&lt;/code&gt; (for applying visual styles to the input field).&lt;/p&gt;
&lt;h4&gt;
  
  
  Message Rendering Logic
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLimitReached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userinput&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxlength&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userinput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxlength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    // ...
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;setContent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setContent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;isLimitReached&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLimitReached&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLimitReached&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"limit-alert"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;limitAlert&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    // ...
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  Accordion Toggle for Density List
&lt;/h3&gt;

&lt;p&gt;To keep the UI clean and uncluttered, only the top 5 character densities are displayed by default. The rest are tucked away in an accordion component that the user can expand or collapse.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shows the top 5 characters by default&lt;/li&gt;
&lt;li&gt;Clicking “See more” reveals the full list&lt;/li&gt;
&lt;li&gt;Clicking “See less” collapses it back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The open/closed state of the accordion is managed using the &lt;code&gt;isOpen&lt;/code&gt; state. Whether the list needs to be split—and which button label to show—is determined dynamically based on the total number of items and the &lt;code&gt;isOpen&lt;/code&gt; state.&lt;/p&gt;
&lt;h4&gt;
  
  
  Accordion Logic
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldSplit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;densityList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsOpen&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;firstList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secondList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cutList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;densityList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setIsOpen&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;firstList&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;secondList&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;shouldSplit&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;density_list_close_label&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;density_list_open_label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  Dark/Light Mode Toggle
&lt;/h3&gt;

&lt;p&gt;The app’s theme is controlled by the &lt;code&gt;isDark&lt;/code&gt; state. Based on its value, a corresponding class (&lt;code&gt;dark-mode&lt;/code&gt; or &lt;code&gt;light-mode&lt;/code&gt;) is applied to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, which handles global styling.&lt;/p&gt;

&lt;p&gt;Clicking the theme toggle icon updates &lt;code&gt;isDark&lt;/code&gt;, triggering a re-render and applying the new theme class.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why Context Wasn’t Used for Theme Toggling
&lt;/h4&gt;

&lt;p&gt;Since &lt;code&gt;isDark&lt;/code&gt; is only used at the top level and doesn’t need to be accessed across many components, there’s no need for React Context. Applying the theme class directly to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; avoids prop drilling or introducing unnecessary context logic, keeping the solution simple and scalable for an app of this size.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Localization Setup
&lt;/h2&gt;

&lt;p&gt;This project implements a scalable structure that allows for easy future expansion into multiple languages without using a full i18n library.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  UI Text in JSON
&lt;/h3&gt;

&lt;p&gt;All display text is stored in &lt;code&gt;locales/en&lt;/code&gt;.json. To add more languages, you can simply create files like &lt;code&gt;locales/ko.json&lt;/code&gt;. The setup supports dynamic values (e.g., &lt;code&gt;{reading_time_value}&lt;/code&gt;) and basic pluralization logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"app_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Analyze your text in real-time."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_placeholder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Start typing here (or paste your text)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"option_title_excl_space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exclude Spaces"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"option_title_char_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Set Character Limit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reading_time_display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approx. reading time: {reading_time_value}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reading_time_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 minute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"one"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt; 1 minute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"other"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt; %d minutes"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;‎ &lt;/p&gt;

&lt;h3&gt;
  
  
  Text Formatting Utility
&lt;/h3&gt;

&lt;p&gt;A custom utility function handles placeholder substitution and pluralization logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pluralRules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\{(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pluralRules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pluralRules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;pluralRules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pluralRules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`{&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Default to keeping the placeholder if missing&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;‎ &lt;/p&gt;

&lt;h3&gt;
  
  
  Passing UI Text Across the App with Context
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;TextContext&lt;/code&gt; is used to provide UI text throughout the app, allowing all components to access text content without prop drilling. Since UI text is typically fixed at release and rarely changes during a single session, using context does not introduce unnecessary re-renders.&lt;/p&gt;

&lt;p&gt;This approach also improves maintainability by centralizing the management of UI text. It enables easier updates and scalability, such as supporting additional languages, without needing to change the component structure. Language support can be extended by enhancing how the app loads and manages language data.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Continued Development
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use rem unit so the UI scales with the browser default font size&lt;/li&gt;
&lt;li&gt;Persist the user’s theme preference using localStorage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://react.dev/reference/react-dom/components/textarea" rel="noopener noreferrer"&gt;React Reference: &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;&lt;/a&gt; by React&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kentcdodds.com/blog/application-state-management-with-react" rel="noopener noreferrer"&gt;Application State Management with React&lt;/a&gt; by Kent C. Dodds&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kentcdodds.com/blog/how-to-use-react-context-effectively" rel="noopener noreferrer"&gt;How to Use React Context effectively&lt;/a&gt; by Kent C. Dodds&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.joshwcomeau.com/react/data-binding/" rel="noopener noreferrer"&gt;Data Binding in React&lt;/a&gt; by Josh Comeau&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.logrocket.com/building-custom-checkbox-react/" rel="noopener noreferrer"&gt;Building a custom checkbox in React&lt;/a&gt; by
Ibadehin Mojeed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://webdesign.tutsplus.com/how-to-make-custom-accessible-checkboxes-and-radio-buttons--cms-32074t" rel="noopener noreferrer"&gt;How to Make Custom Accessible Checkboxes and Radio Buttons&lt;/a&gt; by Sami Keijonen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Author
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nimus-oes, a frontend engineer &amp;amp; localization project manager.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nimus.hashnode.dev/" rel="noopener noreferrer"&gt;Blog (English)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://velog.io/@nimus/posts" rel="noopener noreferrer"&gt;Blog (Korean)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes/frontend-project-collection" rel="noopener noreferrer"&gt;Frontend Project Collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;p&gt;This project is my solution to the Character Counter challenge from FrontendMentor.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Contact Form | Frontend Project #1</title>
      <dc:creator>Nimus</dc:creator>
      <pubDate>Tue, 15 Apr 2025 05:43:37 +0000</pubDate>
      <link>https://dev.to/nimusoes/contact-form-frontend-project-1-148k</link>
      <guid>https://dev.to/nimusoes/contact-form-frontend-project-1-148k</guid>
      <description>&lt;h1&gt;
  
  
  A responsive static contact form built with HTML, CSS, and React
&lt;/h1&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
This is the very first project I built using React, focusing on static page rendering. The app is a single-page contact form that accepts user input. For this project, I concentrated on building a solid foundation for structuring the form and rendering components effectively in React.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes/react-static-contact-form?tab=readme-ov-file#project-requirements" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nimus-oes.github.io/react-static-contact-form/" rel="noopener noreferrer"&gt;Go to Live Demo&lt;/a&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gbbcxw9luvfuyjtm4gt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gbbcxw9luvfuyjtm4gt.jpg" alt="Image description" width="800" height="660"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project Requirements&lt;/li&gt;
&lt;li&gt;Tech Stack&lt;/li&gt;
&lt;li&gt;Features&lt;/li&gt;
&lt;li&gt;What I learned&lt;/li&gt;
&lt;li&gt;Continued Development&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Author&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Project Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Users should be able to provide contact details in the input fields so they can submit the form
&lt;/li&gt;
&lt;li&gt;Name, email, message, and consent for contact fields must be filled to submit the form
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;br&gt;
‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Framework: React 19.0.0&lt;/li&gt;
&lt;li&gt;Build Tool: Vite 6.2.0&lt;/li&gt;
&lt;li&gt;Styling: CSS&lt;/li&gt;
&lt;li&gt;Deployment: GitHub Pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;br&gt;
‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A form containing text, email, checkbox, and dropdown input fields&lt;/li&gt;
&lt;li&gt;Responsive form layout switching from a single-column on mobile to two-column on desktop using a grid&lt;/li&gt;
&lt;li&gt;An achored background image using the pseudo element &lt;code&gt;::before&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Custom dropdown arrow for &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; element&lt;/li&gt;
&lt;li&gt;Accessible text and elements that scales with default font size of browsers using a relative unit rem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;br&gt;
‎ &lt;/p&gt;
&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h3&gt;
  
  
  1. Accessibility from the Perspective of Browser Default Font Size
&lt;/h3&gt;

&lt;p&gt;‎ &lt;/p&gt;
&lt;h4&gt;
  
  
  Misunderstanding
&lt;/h4&gt;

&lt;p&gt;One misconception I had in accessibility is that using rem units alone makes elements scalable to the screen sizes and therefore accessible. However, rem only adapts to the default font size settings of the browser - it does not inherently adjust to screen size unless you specify to do so with media queries.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Responsiveness ≠ Accessibility
&lt;/h4&gt;

&lt;p&gt;There are many requirements to making website accessible for people with different needs, ranging from screen reading to keyboard navigation. While creating a responsive website that adapts its layout to different device sizes can help improve accessibility, it’s only one part of the process and not enough on its own. A responsive website responds to screen size whereas an accessible website responds to a users’ access needs. In this context, using rem is more about accessibility, as it allows text to scale based on the user’s preferred default size.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Browser Default Font Sizes Vary
&lt;/h4&gt;

&lt;p&gt;For accessibility, it’s essential to consider that users may increase their browser’s default font size for better readability. Accessible designs maintain a solid layout that doesn't break even when the font size is scaled up significantly.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Why Use rem Instead of px for Accessibility
&lt;/h4&gt;

&lt;p&gt;The rem unit is based on the root font size, so if users increase the default font size on the browser, elements sized in rem will adjust accordingly. In contrast, px remains fixed, regardless of user settings. rem is generally better for font sizes and layout elements that need to be flexible to match user preferences.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  rem Isn't a One-Size-Fits-All Solution
&lt;/h4&gt;

&lt;p&gt;There are cases where px is still useful, such as for borders or padding, where scaling with font size isn’t necessary and beneficial to users. Before applying rem to everything, it’s important to consider whether a value actually needs to scale when a user increases their browser’s default font size. Testing these choices by adjusting the browser font size can help determine when to use rem versus px.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sz11oycfpsahd7ireks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sz11oycfpsahd7ireks.png" alt="Image description" width="800" height="302"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;No room for padding to be scaled up here. As large paddings don't add value when the space is already limited, padding stays in px.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;


&lt;h3&gt;
  
  
  2. Grid vs. Flexbox
&lt;/h3&gt;

&lt;p&gt;Both CSS Grid and Flexbox could be used to achieve this form layout, but each serves different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grid&lt;/strong&gt;: Ideal for two-dimensional layouts (rows and columns), equal-sized areas, and larger page structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexbox&lt;/strong&gt;: Best for one-dimensional layouts (row or column), dynamic content alignment, and layouts where content size drives positioning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎   &lt;/p&gt;
&lt;h4&gt;
  
  
  Choosing the Right Layout for the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The form in this project switches between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-column layout on mobile, where all items take full width.&lt;/li&gt;
&lt;li&gt;Two-column layout on desktop, where some items remain full-width while others take up half of the form container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this with flexbox, you would need to assign &lt;code&gt;flex&lt;/code&gt; values to control the width of each form item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* 
flex-basis determines the item's base width, 
while flex-grow and flex-shrink allow the items to adjust dynamically.
*/&lt;/span&gt;

&lt;span class="nc"&gt;.desktop-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.half-width-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;48%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.full-width-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;‎ &lt;/p&gt;

&lt;p&gt;The same layout can be implemented with grid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.desktop-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.half-width-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.full-width-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;grid-column&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;‎ &lt;/p&gt;

&lt;p&gt;For this project, I chose Grid because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It provides a simpler system with &lt;code&gt;span&lt;/code&gt;, making it easier to control column spans.&lt;/li&gt;
&lt;li&gt;The grid system is more intuitive to understand the structure&lt;/li&gt;
&lt;li&gt;The form layout is not dynamically determined by content size—it has just two fixed types: half-width or full-width.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h4&gt;
  
  
  Aligning Form Items
&lt;/h4&gt;

&lt;p&gt;While the overall form layout benefits from grid, individual form elements like , , and  require one-dimensional vertical alignment. For this, I used flexbox as a quick and effective solution.&lt;/p&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Checklist for Form
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; for each input field.&lt;/li&gt;
&lt;li&gt;Use appropriate type attributes for different input fields.&lt;/li&gt;
&lt;li&gt;Set minimum/maximum values for number inputs and define patterns for password inputs.&lt;/li&gt;
&lt;li&gt;Assign &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, and &lt;code&gt;value&lt;/code&gt; attributes where needed.&lt;/li&gt;
&lt;li&gt;Use the required attribute for mandatory fields.&lt;/li&gt;
&lt;li&gt;Ensure the &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; resize handle behaves correctly according to the design.&lt;/li&gt;
&lt;li&gt;Properly adjust the highlight effect for &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; fields.&lt;/li&gt;
&lt;li&gt;Maintain adequate spacing between radio buttons/checkboxes and their text labels.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;padding&lt;/code&gt; inside input fields to prevent text from appearing too close to the border.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Continued Development
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Would it be better to apply gradient background in CSS rather than using an image file in terms of performance?&lt;/li&gt;
&lt;li&gt;Apply basic data validation to form&lt;/li&gt;
&lt;li&gt;Remove highlight effect on the input fields&lt;/li&gt;
&lt;li&gt;Create a custom dropdown component and apply style to dropdown options&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jimbyrne.co.uk/what-is-responsive-website-design-and-how-does-it-relate-to-accessibility/" rel="noopener noreferrer"&gt;What is responsive website design and how does it relate to accessibility&lt;/a&gt; by Jim Byrne&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/" rel="noopener noreferrer"&gt;The Surprising Truth About Pixels and Accessibility&lt;/a&gt; by Josh Comeau&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;h2&gt;
  
  
  Author
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nimus-oes, a frontend engineer &amp;amp; localization project manager.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/nimusoes"&gt;Blog (English)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://velog.io/@nimus/posts" rel="noopener noreferrer"&gt;Blog (Korean)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Nimus-oes/frontend-project-collection" rel="noopener noreferrer"&gt;Frontend Project Collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;‎ &lt;/p&gt;

&lt;p&gt;This project is my solution to the Contact Form challenge from devChallenges.io&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
