<?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: Eric Luna</title>
    <description>The latest articles on DEV Community by Eric Luna (@ericlunadev).</description>
    <link>https://dev.to/ericlunadev</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%2F659776%2Fe56347c1-add7-499a-b1ef-7d4ca98c99e6.jpeg</url>
      <title>DEV Community: Eric Luna</title>
      <link>https://dev.to/ericlunadev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ericlunadev"/>
    <language>en</language>
    <item>
      <title>Configuring The Ultimate Django/Tailwind Experience (without Node)</title>
      <dc:creator>Eric Luna</dc:creator>
      <pubDate>Thu, 03 Mar 2022 21:34:46 +0000</pubDate>
      <link>https://dev.to/ericlunadev/configuring-the-ultimate-djangotailwind-experience-without-node-349m</link>
      <guid>https://dev.to/ericlunadev/configuring-the-ultimate-djangotailwind-experience-without-node-349m</guid>
      <description>&lt;p&gt;Tailwind is all over the place, it has gained quite a lot of traction in the last years. I was hesitant at first but it eventually grew into me and here we are!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TLDR: If you want to skip this post just clone the final boilerplate &lt;a href="https://github.com/Eric-Luna/django-tailwind-boilerplate"&gt;here&lt;/a&gt; and follow the installation instructions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Previously, if you wanted to use Tailwind on Django you had to have Node.js and npm installed to use it.&lt;/p&gt;

&lt;p&gt;NPM is not common in Django’s ecosystem so installing Tailwind came with a bunch of headaches but those days are gone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/adamwathan"&gt;Adam&lt;/a&gt; and his amazing team at Tailwind Labs recently released Tailwind’s Standalone CLI, a self-contained executable that you can use just like the regular Tailwind’s CLI but without Node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Go to your Django’s project directory and grab the executable for your platform from the &lt;a href="https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.0.23"&gt;latest release&lt;/a&gt; on GitHub, making sure to give it executable permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example for macOS arm64&lt;/span&gt;
curl &lt;span class="nt"&gt;-sLO&lt;/span&gt; https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x tailwindcss-macos-arm64
&lt;span class="nb"&gt;mv &lt;/span&gt;tailwindcss-macos-arm64 tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On your &lt;code&gt;static/css&lt;/code&gt; folder create a file called &lt;code&gt;input.css&lt;/code&gt; and add these directives:&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="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a file called &lt;code&gt;output.css&lt;/code&gt;. Leave that empty (the CLI will take care of this) and import this asset on your template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- home.html --&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% static 'css/output.css' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s create a &lt;code&gt;tailwind.config.js&lt;/code&gt; file using the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./tailwindcss init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s the file you will be editing if you want to override or extend the default Tailwind config. You can read more about it &lt;a href="https://tailwindcss.com/docs/configuration"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first config setting that we are going to use is &lt;code&gt;content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We will pass a path to the files that we want to watch for Tailwind classes.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./django_tailwind_boilerplate/**/*.{html,js}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&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="na"&gt;plugins&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the above setup, we are telling the CLI watcher to check every &lt;code&gt;.html&lt;/code&gt; and &lt;code&gt;.js&lt;/code&gt; file in the project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ You should never conditionally construct utility classes. Doing this (&lt;code&gt;’text-${hasError ? ‘red’ : ‘green’}-400&lt;/code&gt;) because it will prevent the watcher from picking up the styles.&lt;br&gt;
To fix this example do: &lt;code&gt;{hasError ? ‘text-red-400’ : ‘text-green-400’}&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now run the CLI watcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./tailwindcss &lt;span class="nt"&gt;-i&lt;/span&gt; static/css/input.css &lt;span class="nt"&gt;-o&lt;/span&gt; static/css/output.css &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will notice that &lt;code&gt;output.css&lt;/code&gt; now has a &lt;a href="https://tailwindcss.com/docs/preflight"&gt;Tailwind’s preflight&lt;/a&gt; (a CSS reset). This will help you address cross-browser inconsistencies and start a project with a good set of default CSS attributes.&lt;/p&gt;

&lt;p&gt;We can add any of Tailwind utility classes to our templates and the watcher will immediately add it to &lt;code&gt;output.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- home.html --&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center justify-center w-full h-screen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-4xl text-[#51BE95] font-mono"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello Tailwind&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;output&lt;/span&gt;&lt;span class="nc"&gt;.css&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;preflight&lt;/span&gt; &lt;span class="nt"&gt;classes&lt;/span&gt;

&lt;span class="nc"&gt;.static&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;static&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.flex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.h-screen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.w-full&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nc"&gt;.items-center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.justify-center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.font-mono&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ui-monospace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SFMono-Regular&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Menlo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Monaco&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Consolas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Liberation Mono"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Courier New"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.text-4xl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.text-&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="err"&gt;\#51&lt;/span&gt;&lt;span class="nt"&gt;BE95&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--tw-text-opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;81&lt;/span&gt; &lt;span class="m"&gt;190&lt;/span&gt; &lt;span class="m"&gt;149&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--tw-text-opacity&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;h2&gt;
  
  
  ⭐Bonus Tip
&lt;/h2&gt;

&lt;p&gt;Django will restart the server on every change on &lt;code&gt;.py&lt;/code&gt; files but if you want to see your changes on template files you will need to reload your browser.&lt;/p&gt;

&lt;p&gt;To improve that experience, install &lt;a href="https://github.com/adamchainz/django-browser-reload"&gt;django-browser-reload&lt;/a&gt;. After that, any changes you make on a template will be immediately reflected on your browser. It’s pretty much like having React’s hot module reloading at your disposal!&lt;/p&gt;

&lt;p&gt;Get the boilerplate to start your next Django project with Tailwind &lt;a href="https://github.com/Eric-Luna/django-tailwind-boilerplate"&gt;here&lt;/a&gt;. (django-browser-reload included!)&lt;/p&gt;

</description>
      <category>django</category>
      <category>tailwindcss</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Dark mode con TailwindCSS y NextJS</title>
      <dc:creator>Eric Luna</dc:creator>
      <pubDate>Thu, 22 Jul 2021 12:31:15 +0000</pubDate>
      <link>https://dev.to/ericlunadev/dark-mode-con-tailwindcss-y-nextjs-3ma0</link>
      <guid>https://dev.to/ericlunadev/dark-mode-con-tailwindcss-y-nextjs-3ma0</guid>
      <description>&lt;p&gt;Cada día me sorprendo más de la velocidad para escribir CSS que me da TailwindCSS.&lt;/p&gt;

&lt;p&gt;Hace un par de semanas implementé el tema oscuro de este sitio y fue una experiencia sin ningún tipo de fricción.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Configuración inicial
&lt;/h2&gt;

&lt;p&gt;En la raíz de nuestro proyecto vamos a tener un archivo &lt;code&gt;tailwind.config.js&lt;/code&gt; después de haber instalado Tailwiind:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt;&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&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="na"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class&lt;/span&gt;&lt;span class="dl"&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;Lo que hace esto es habilitar el modo oscuro cuando agregamos la clase &lt;code&gt;dark&lt;/code&gt; a la etiqueta &lt;code&gt;html&lt;/code&gt; de nuestro proyecto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"dark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ....
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez habilitado el tema oscuro vamos a poder agregar el prefijo &lt;code&gt;dark&lt;/code&gt; a nuestras clases en Tailwind y de esta manera aplicar esos estilos dependiendo de si está habilitado o no el tema oscuro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500 dark:text-red-500 text-xl font-mono"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sumate al lado oscuro&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  👁️ Detectando las preferencias del usuario
&lt;/h2&gt;

&lt;p&gt;Hasta el momento hardcodeamos la clase &lt;code&gt;dark&lt;/code&gt; en la etiqueta &lt;code&gt;html&lt;/code&gt;. Ahora vamos a ver como podemos cambiarla dinamicamente, permitirle al usuario elegir su tema preferido y persistirlo en memoria para que la próxima vez que entre al sitio se encuentre como lo dejó.&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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getInitialColorMode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Checkeamos si hay guardado algo en localStorage&lt;/span&gt;
    &lt;span class="c1"&gt;// sinó usamos la preferencia del sistema oprativo del usuario&lt;/span&gt;
    &lt;span class="c1"&gt;// Agregamos o removemos la clase según sea necesario&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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;useEffect&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="nx"&gt;getInitialColorMode&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="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="si"&gt;}&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;Incializamos nuestra aplicación, ni bien montamos el componente raíz llamamos a &lt;code&gt;getInitialColorMode&lt;/code&gt; y &lt;code&gt;getInitialColorMode&lt;/code&gt; setea la clase correcta en el HTML.&lt;/p&gt;

&lt;p&gt;Hasta ahí todo viene perfecto pero hay un solo problema...&lt;/p&gt;

&lt;h2&gt;
  
  
  📜 SSR
&lt;/h2&gt;

&lt;p&gt;Cuando navegamos a nuestra página, hacemos una petición al servidor. El servidor nos devuelve el HTML, CSS y JS que nuestro navegador va a usar para visualizar el sitio. El problema es que el servidor no tiene manera de saber la preferencia del usuario ni acceder a los valores en localStorage porque ese código se está ejecutando del lado del cliente.&lt;/p&gt;

&lt;p&gt;El flujo es más o menos el siguiente:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El cliente navega a la dirección de nuestro sitio.&lt;/li&gt;
&lt;li&gt;El servidor devuelve el HTML, CSS y JS que va a usar el cliente.&lt;/li&gt;
&lt;li&gt;El cliente carga el HTML y el CSS.&lt;/li&gt;
&lt;li&gt;Renderiza el tema por defecto del sitio.&lt;/li&gt;
&lt;li&gt;React se carga y rehidrata la página.&lt;/li&gt;
&lt;li&gt;Al correr Javascript nos damos cuenta que el usuario preferia usar el tema oscuro&lt;/li&gt;
&lt;li&gt;La página se renderiza otra vez, esta vez con el tema oscuro.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esto nos deja un hermoso efecto de parpadeo que no queremos en nuestra web.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iCNxbog1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ericluna.dev/_next/image%3Furl%3D%252Fblog%252Fparpadeo_tema_oscuro.GIF%26w%3D1920%26q%3D75" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iCNxbog1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ericluna.dev/_next/image%3Furl%3D%252Fblog%252Fparpadeo_tema_oscuro.GIF%26w%3D1920%26q%3D75" alt="Parpadeo al cargar la página"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¿La solución?&lt;/p&gt;

&lt;p&gt;Injectar una etiqueta &lt;code&gt;script&lt;/code&gt; que vaya antes del contenido principal de nuestro &lt;code&gt;body&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🛑 Bloqueando HTML
&lt;/h2&gt;

&lt;p&gt;Para hacer esto en NextJS vamos a tener que sobreescribir &lt;code&gt;_document.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Vamos a hacer esto cada vez que necesitemos agregar más logica a las etiquetas &lt;code&gt;html&lt;/code&gt; y &lt;code&gt;body&lt;/code&gt; de nuestra página.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextScript&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;MyDocument&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;getInitialProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;initialProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInitialProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;...&lt;/span&gt;&lt;span class="nx"&gt;initialProps&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;render&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="nc"&gt;Html&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;Head&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;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'/checkUserDarkMode.js'&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;Head&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;body&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;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;NextScript&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;body&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;Html&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;MyDocument&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este es el &lt;code&gt;_document.js&lt;/code&gt; por defecto. Lo único que estamos agregando es el  que corre &lt;code&gt;checkUserDarkMode.js&lt;/code&gt;. Este script tiene que estar ubicado en la carpeta &lt;code&gt;public&lt;/code&gt; de nuestro proyecto y hace lo siguiente:&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;(&lt;/span&gt;&lt;span class="kd"&gt;function&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="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎣 Custom Hook
&lt;/h2&gt;

&lt;p&gt;Para trabajar con el tema oscuro en nuestra aplicación podemos crear un hook que nos permita cambiar entre los distintos temas.&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="c1"&gt;// useColorScheme.jsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&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;useColorScheme&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isDarkInitial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isDarkInitial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme: dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsDark&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDarkInitial&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;toogleTheme&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="nx"&gt;setIsDark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;dark&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isDark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toogleTheme&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useColorScheme&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con todo esto en su lugar ahora podemos facilmente acceder al tema que está en uso y switchear entre ellos:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useColorScheme&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../Hooks/useColorScheme&lt;/span&gt;&lt;span class="dl"&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;isDark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toogleTheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useColorScheme&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;MiComponente&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="k"&gt;return&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="nx"&gt;isDark&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MoonIcon&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;toogleTheme&lt;/span&gt;&lt;span class="si"&gt;}&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;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SunIcon&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;toogleTheme&lt;/span&gt;&lt;span class="si"&gt;}&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔗 Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📖 &lt;a href="https://nextjs.org/docs/advanced-features/custom-document"&gt;Custom Document en NextJS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://tailwindcss.com/docs/dark-mode"&gt;Dark mode de TailwindCSS&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tailwindcss</category>
      <category>nextjs</category>
      <category>ssr</category>
      <category>darkmode</category>
    </item>
    <item>
      <title>Automatizando formularios con Selenium en Python</title>
      <dc:creator>Eric Luna</dc:creator>
      <pubDate>Thu, 01 Jul 2021 13:31:40 +0000</pubDate>
      <link>https://dev.to/ericlunadev/automatizando-formularios-con-selenium-en-python-18hf</link>
      <guid>https://dev.to/ericlunadev/automatizando-formularios-con-selenium-en-python-18hf</guid>
      <description>&lt;p&gt;Las tareas repetitivas son peor de lo que pensas.&lt;/p&gt;

&lt;p&gt;Esa tarea que haces durante 15 minutos todos los días se lleva 91.25 horas de tu año.&lt;/p&gt;

&lt;p&gt;Los números son mucho más aterradores si aumentamos esos 15 minutos a 1 hora.&lt;/p&gt;

&lt;p&gt;¿Cómo hacemos entonces para automatizar esas tareas triviales y recuperar nuestro tiempo?&lt;/p&gt;

&lt;p&gt;Acá es donde entra Python al rescate.&lt;/p&gt;

&lt;p&gt;Hoy vamos a ver cómo automatizar la web usando Python y una fantástica libreria para la automatización de navegadores llamada Selenium.&lt;/p&gt;

&lt;p&gt;Te voy a guiar paso a paso y vamos a aprender a: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configurar un entorno de automatización,&lt;/li&gt;
&lt;li&gt;Localizar elementos en el navegador&lt;/li&gt;
&lt;li&gt;Interactuar con ellos&lt;/li&gt;
&lt;li&gt;Y algunos casos extraños que te podes encontrar&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚙️ Configurar un entorno de automatización
&lt;/h2&gt;

&lt;p&gt;Requerimientos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tener instalado cualquier version de Python 3.X en nuestra computadora&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Driver
&lt;/h3&gt;

&lt;p&gt;Lo primero que vamos a necesitar es un driver.&lt;/p&gt;

&lt;p&gt;En este ejemplo vamos a usar chromedriver, el driver para Chrome, pero te podes descargar el driver de cualquier navegador que uses. (&lt;a href="https://chromedriver.chromium.org/downloads" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, &lt;a href="https://github.com/mozilla/geckodriver/releases/" rel="noopener noreferrer"&gt;Firefox&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;ℹ️ Asegurate siempre de descargar la versión del driver que soporte la versión del navegador que estás usando&lt;/p&gt;

&lt;p&gt;Después vamos a crear la carpeta de nuestro proyecto, crear un entorno virtual e instalar Selenium.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

pip &lt;span class="nb"&gt;install &lt;/span&gt;Selenium


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

&lt;/div&gt;

&lt;p&gt;Podes ir probando todo lo que vayas aprendiendo en &lt;a href="https://ericluna.dev/formTest" rel="noopener noreferrer"&gt;este formulario ficticio&lt;/a&gt; que creé para que puedan correr sus scripts sin romper nada en ningún lado 🔨&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fec8arr330ewvcmi50b51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fec8arr330ewvcmi50b51.png" alt="Formulario para testear"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para inicializar Selenium tenemos que escribir lo siguiente en nuestro script:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Importamos webdriver desde selenium
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;

&lt;span class="c1"&gt;# Le pasamos la dirección donde descargamos nuestro driver
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/home/eric/drivers/chromedriver&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Le pasamos la URL del sitio que vamos a automatizar
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://ericluna.dev/formTest&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Si ejecutamos el programa ahora, se nos va a abrir una instancia de Chrome y va a navegar automaticamente hacia la URL que le pasamos.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Localizando elementos
&lt;/h2&gt;

&lt;p&gt;Ahora que tenemos nuestro navegador abierto, vamos a ver como podemos seleccionar elementos del DOM para luego interactuar con ellos.&lt;/p&gt;

&lt;p&gt;Hay muchas maneras de hacerlo, la más fácil es buscarlos por id.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# &amp;lt;input id="MI_ID"&amp;gt;&lt;/span&gt;
driver.select_element_by_id&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MI_ID'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Para ver el id de un elemento tenés que abrir la consola de Chrome &lt;code&gt;(Ctrl + Shift + I)&lt;/code&gt; y buscar el attributo id en el campo que queremos seleccionar.&lt;/p&gt;

&lt;p&gt;Esto no es posible siempre. A veces vamos a querer interactuar con elementos que no tienen un id.&lt;/p&gt;

&lt;p&gt;¿Qué hacemos en esos casos?&lt;/p&gt;

&lt;p&gt;Bueno, por suerte hay muchas formas de seleccionar un elemento.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;find_element_by_id&lt;/li&gt;
&lt;li&gt;find_element_by_name&lt;/li&gt;
&lt;li&gt;find_element_by_xpath&lt;/li&gt;
&lt;li&gt;find_element_by_link_text&lt;/li&gt;
&lt;li&gt;find_element_by_partial_link_text&lt;/li&gt;
&lt;li&gt;find_element_by_tag_name&lt;/li&gt;
&lt;li&gt;find_element_by_class_name&lt;/li&gt;
&lt;li&gt;find_element_by_css_selector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si no queremos andar con vueltas, la que nunca falla es Xpath.&lt;/p&gt;

&lt;p&gt;Podés aprendar la sintaxis de cómo escribir un selector con Xpath o simplemente inspeccionar un elemento en &lt;code&gt;Chrome → Click derecho → Copiar → Copiar Xpath&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚡ Interacciones
&lt;/h2&gt;

&lt;p&gt;Hasta ahora hemos logrado abrir una instancia de Chrome y seleccionar un elemento. Ahora vamos a ver como podemos interactuar con el mismo.&lt;/p&gt;

&lt;p&gt;Vamos a poder accedder a distintas acciones dependiendo del elementos que seleccionemos.&lt;/p&gt;

&lt;p&gt;Si seleccionamos un input de tipo texto por ejemplo, vamos a poder usar &lt;code&gt;send_keys&lt;/code&gt; para pasarle el texto que queremos escribir.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# &amp;lt;input id="nombre" type"text"&amp;gt;
&lt;/span&gt;&lt;span class="n"&gt;input_nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nombre&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;input_nombre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Eric Luna&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;En el  caso de elementos clickeables, vamos a poder usar &lt;code&gt;click&lt;/code&gt; para clickearlos pero si estas interactuando con el boton de "Enviar" de un formulario deberías usar &lt;code&gt;submit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Basicamente, cualquier cosas que podamos hacer manualmente en el navegador, lo podemos hacer automaticamente con Selenium.&lt;/p&gt;

&lt;p&gt;Ya sea navegar a distintas URLs, interacturar con ventanas modales, ver las cookies, arrastrar elementos, etc,etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⏱️ Tipos De Espera
&lt;/h2&gt;

&lt;p&gt;Cuando cargamos una URL en el navegador de Selenium, es erroneo asumir que todo el contenido va a estar presente. Ya sea que el sitio este usando AJAX o que tu conexión a internet esté más lenta ese día, hay elementos que van a necesitar más tiempo para renderizarse antes de que estén disponibles para que los selecciones.&lt;/p&gt;

&lt;p&gt;Por eso es que tenemos que....esperar.&lt;/p&gt;

&lt;p&gt;Selenium nos provee dos tipos de espera. &lt;/p&gt;

&lt;h3&gt;
  
  
  Esperas Implícitas
&lt;/h3&gt;

&lt;p&gt;Según la documentación: "Una espera implícita le dice al WebDriver que pida los cambios al DOM por un tiempo determinado cuando está tratando de encontrar un elemento que no está inmediatamente disponible.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;implicitly_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
&lt;span class="n"&gt;input_nombre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_element_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nombre&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;# Va a tirar un error si no encuentra el elemento después de 10 segundos&lt;br&gt;
&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Esperas Explícitas&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Las esperas explícitas esperan a que se cumpla una cierta condición antes de continuar. De esta manera solamente esperamos el tiempo necesario, ni mas ni menos.&lt;/p&gt;

&lt;p&gt;Podemos por ejemplo, esperar a que que el elemento sea clickeable en el DOM, o podemos esperar a que el elemento tengo un texto en particular.&lt;/p&gt;

&lt;p&gt;Selenium viene con un montón de distintas esperas explícitas que podemos usar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title_is&lt;/li&gt;
&lt;li&gt;title_contains&lt;/li&gt;
&lt;li&gt;presence_of_element_located&lt;/li&gt;
&lt;li&gt;visibility_of_element_located&lt;/li&gt;
&lt;li&gt;visibility_of&lt;/li&gt;
&lt;li&gt;presence_of_all_elements_located&lt;/li&gt;
&lt;li&gt;text_to_be_present_in_element&lt;/li&gt;
&lt;li&gt;text_to_be_present_in_element_value&lt;/li&gt;
&lt;li&gt;frame_to_be_available_and_switch_to_it&lt;/li&gt;
&lt;li&gt;invisibility_of_element_located&lt;/li&gt;
&lt;li&gt;element_to_be_clickable&lt;/li&gt;
&lt;li&gt;staleness_of&lt;/li&gt;
&lt;li&gt;element_to_be_selected&lt;/li&gt;
&lt;li&gt;element_located_to_be_selected&lt;/li&gt;
&lt;li&gt;element_selection_state_to_be&lt;/li&gt;
&lt;li&gt;element_located_selection_state_to_be&lt;/li&gt;
&lt;li&gt;alert_is_present&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pero si ninguna de estas te sirven, podes implementar la tuya facilmente.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📖 &lt;a href="https://selenium-python.readthedocs.io/" rel="noopener noreferrer"&gt;Documentación de Selenium para Python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⬇️ &lt;a href="https://chromedriver.chromium.org/downloads" rel="noopener noreferrer"&gt;Driver para Chrome&lt;/a&gt;, &lt;/li&gt;
&lt;li&gt;⬇️ &lt;a href="https://github.com/mozilla/geckodriver/releases/" rel="noopener noreferrer"&gt;Driver para Firefox&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automatizacion</category>
      <category>selenium</category>
      <category>python</category>
    </item>
  </channel>
</rss>
