<?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: Mukul Dharwadkar</title>
    <description>The latest articles on DEV Community by Mukul Dharwadkar (@mukul_d).</description>
    <link>https://dev.to/mukul_d</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%2F1451604%2F2adb7b58-f5c5-4982-ad88-6037c83f42dc.jpg</url>
      <title>DEV Community: Mukul Dharwadkar</title>
      <link>https://dev.to/mukul_d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mukul_d"/>
    <language>en</language>
    <item>
      <title>Maruti-Zsh: a Custom, High-Performance Zsh Engine</title>
      <dc:creator>Mukul Dharwadkar</dc:creator>
      <pubDate>Wed, 20 May 2026 06:33:08 +0000</pubDate>
      <link>https://dev.to/mukul_d/maruti-zsh-a-custom-high-performance-zsh-engine-jek</link>
      <guid>https://dev.to/mukul_d/maruti-zsh-a-custom-high-performance-zsh-engine-jek</guid>
      <description>&lt;p&gt;We've all been there. You install &lt;a href="https://www.zsh.org" rel="noopener noreferrer"&gt;Zsh&lt;/a&gt;, and the very next recommendation you see online is to install &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Oh-My-Zsh&lt;/a&gt;. For a long time, I did exactly that. But over time, the "magic" of heavy frameworks started to wear thin.&lt;/p&gt;

&lt;p&gt;Recently, my old web server hosted on a 2011 Macbook Pro running Ubuntu 22.04 started failing and was on life support. I was not ready to spend a lot of money on a replacement computer and was scouring Facebook Marketplace, Craigslist, Nextdoor to get a cheap computer. Incidentally I got a free PC from 2010 with a potentially bad hard drive. &lt;/p&gt;

&lt;p&gt;I was ready to troubleshoot and make it work and I set out to repurpose that PC as a lean web server. After extensive research on the right OS, I decided that I will use Debian 13. On hardware of that vintage, every megabyte of RAM and every CPU cycle matters. When I loaded a standard framework onto it, I noticed a distinct, frustrating lag before the prompt appeared.&lt;/p&gt;

&lt;p&gt;The terminal shouldn't lag. It should feel like the wind—instant, unburdened, and powerful.&lt;/p&gt;

&lt;p&gt;So I stripped everything out and built a native, modular Zsh configuration engine from scratch. I named it &lt;strong&gt;Maruti-Zsh&lt;/strong&gt;, after the ancient Indian deity of the wind, celebrated for immense strength hidden within a modest form.&lt;/p&gt;

&lt;p&gt;Here is how I built it, the breaking point that almost locked me out of my own server, and why you might want to drop the frameworks too.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Philosophy: Zero Frameworks, Zero Bloat&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The core issue with modern command-line setups isn't Zsh itself; it's the "black box" plugins that load hundreds of lines of third-party script architecture you will never use.&lt;/p&gt;

&lt;p&gt;Maruti-Zsh returns to native Zsh internals. By targeting the Zsh Line Editor (ZLE) directly, you can achieve sophisticated features—like syntax highlighting, auto-suggestions, and deep history tracking—with instant startup times.&lt;/p&gt;

&lt;p&gt;To keep things organized without a framework, I built a modular architecture. Instead of one massive, unmanageable &lt;code&gt;.zshrc&lt;/code&gt; file, the configuration lives in a dedicated directory:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.maruti-zsh/&lt;/code&gt;&lt;br&gt;&lt;br&gt;
  &lt;code&gt;└── .zsh/&lt;/code&gt;&lt;br&gt;&lt;br&gt;
       &lt;code&gt;├── key-bindings.zsh    # Custom ZLE widgets and shortcuts&lt;/code&gt;&lt;br&gt;&lt;br&gt;
       &lt;code&gt;├── zsh-aliases.zsh     # Navigation and tool aliases&lt;/code&gt;&lt;br&gt;&lt;br&gt;
   &lt;code&gt;├── zsh-completion.zsh  # Completion system and zstyle config&lt;/code&gt;&lt;br&gt;&lt;br&gt;
       &lt;code&gt;├── zsh-history.zsh     # History size and optimizations&lt;/code&gt;&lt;br&gt;&lt;br&gt;
       &lt;code&gt;└── plugins/            # Pure git-cloned plugins&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The loader in &lt;code&gt;.zshrc&lt;/code&gt; sources everything in that directory with a single glob — no hardcoded entries, no framework overhead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;for f in ~/.maruti-zsh/.zsh/*.zsh(N); do source "$f"; done&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;(N)&lt;/code&gt; qualifier is a Zsh glob flag that suppresses errors if the directory is empty, making this safe to run even on a fresh install.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Engine: Modular Sourcing (And How I Broke It)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;My first attempt at the loader looked like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Do not copy this version!&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;for f in ~/.zsh/configs/**/*.zsh(N); do source "$f"; done&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It worked beautifully on my laptop. But when I deployed it to the server and ran &lt;code&gt;source ~/.zshrc&lt;/code&gt;, the terminal instantly kicked me out, locked the session, and refused to let me back in, screaming about running out of &lt;strong&gt;BUFFER&lt;/strong&gt; space.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Lesson&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I had accidentally created a circular recursive loop. The &lt;code&gt;**&lt;/code&gt; globbing pattern was too broad and caught a file that re-triggered the initialization sequence. Because each &lt;code&gt;source&lt;/code&gt; execution consumes stack memory, the shell suffered an instant buffer overflow and the OS killed the process.&lt;/p&gt;

&lt;p&gt;If you ever find yourself locked out by a broken shell config, you can bypass your initialization files over SSH like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh -t user@server_ip "zsh --no-rcs"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once back in, the fix was simple: scope the glob precisely to the &lt;code&gt;.zsh/&lt;/code&gt; directory using &lt;code&gt;*.zsh&lt;/code&gt; instead of &lt;code&gt;**/*.zsh&lt;/code&gt;, so it only sources files immediately in that folder with no recursive descent:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;for f in ~/.maruti-zsh/.zsh/*.zsh(N); do source "$f"; done&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  With the engine safely loading files, I could start building out the actual configuration modules.  
&lt;/h2&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;History That Actually Works&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before any widgets, the first thing to get right is history. The defaults Zsh ships with are surprisingly barebones. Here is the full configuration in &lt;code&gt;zsh-history.zsh&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HISTFILE=~/.zsh_history&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;HISTSIZE=10000&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;SAVEHIST=10000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setopt HIST_IGNORE_ALL_DUPS  # Don't record duplicate commands&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;setopt HIST_REDUCE_BLANKS    # Strip superfluous whitespace&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;setopt HIST_VERIFY           # Show the expanded command before running it&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;setopt SHARE_HISTORY         # Share history across all open terminals instantly&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;setopt HIST_IGNORE_SPACE     # Commands prefixed with a space are never saved&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That last one — &lt;code&gt;HIST_IGNORE_SPACE&lt;/code&gt; — is genuinely useful for secrets and one-off commands you don't want persisted. Prefix any command with a space and it disappears from history entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Tab Completion: Native Power, No Plugin Required&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One of Zsh's most underappreciated advantages over Bash is its built-in completion system. It doesn't need a plugin — it just needs to be initialized and configured. This lives in its own module, &lt;code&gt;zsh-completion.zsh&lt;/code&gt;, keeping it separate and easy to modify&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Initialize the completion system&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;autoload -Uz compinit &amp;amp;&amp;amp; compinit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Arrow-key navigable completion menu&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zstyle ':completion:*' menu select&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Colorized completions using existing LS_COLORS&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Group completions by category&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zstyle ':completion:*' group-name ''&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Label each category group&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zstyle ':completion:*:descriptions' format '%F{yellow}-- %d --%f'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Auto-find newly installed executables without restarting the shell&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zstyle ':completion:*' rehash true&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What Each Line Does&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;autoload -Uz compinit &amp;amp;&amp;amp; compinit&lt;/code&gt; activates the entire completion engine. Without this, Zsh's tab completion falls back to basic filename expansion — functional, but nowhere near its potential.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;menu select&lt;/code&gt; turns the completion list into a navigable menu. Instead of cycling blindly through candidates with repeated Tab presses, you get a visual list you can move through with arrow keys and select with Enter. This alone is worth the setup.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;list-colors&lt;/code&gt; pipes your existing &lt;code&gt;$LS_COLORS&lt;/code&gt; environment variable into the completion menu, so directories, executables, symlinks, and files all render in the same colors you already see in &lt;code&gt;ls&lt;/code&gt; output. The cryptic &lt;code&gt;${(s.:.)LS_COLORS}&lt;/code&gt; is Zsh parameter expansion syntax — &lt;code&gt;(s.:.)&lt;/code&gt; splits the string on &lt;code&gt;:&lt;/code&gt; into an array that &lt;code&gt;zstyle&lt;/code&gt; can consume.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;group-name ''&lt;/code&gt; tells Zsh to separate completions into named categories — files in one group, shell builtins in another, external commands in another — rather than dumping everything into a single flat list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;format '%F{yellow}-- %d --%f'&lt;/code&gt; adds a yellow category label above each group. &lt;code&gt;%d&lt;/code&gt; is replaced with the group description, and &lt;code&gt;%F{yellow}&lt;/code&gt; / &lt;code&gt;%f&lt;/code&gt; are Zsh prompt color codes. Small touch, significant readability improvement.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rehash true&lt;/code&gt; tells the completion system to scan &lt;code&gt;$PATH&lt;/code&gt; for new executables automatically. Without this, if you install a new tool mid-session, Zsh won't offer it as a completion candidate until you start a new shell or run &lt;code&gt;rehash&lt;/code&gt; manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;A Note on Case Sensitivity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Zsh's completion system supports case-insensitive matching via &lt;code&gt;zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'&lt;/code&gt;. Maruti-Zsh deliberately leaves this out. Linux is a case-sensitive operating system and the completion system should reflect that — if you type &lt;code&gt;doc&lt;/code&gt;, you should get &lt;code&gt;doc&lt;/code&gt;, not &lt;code&gt;Documents&lt;/code&gt;. The discipline of knowing your filenames matters, and the completion menu shouldn't paper over it.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Power Moves: Custom ZLE Widgets&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Without a framework, how do you get premium features? You write small, targeted functions using the Zsh Line Editor (ZLE). ZLE is essentially a tiny text editor living inside your command line — it lets you define functions that manipulate the command buffer directly, then bind them to any key combination you want.&lt;/p&gt;

&lt;p&gt;Here are the custom shortcuts in Maruti-Zsh that change how you interact with the shell.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. The Sudo Toggle (Alt+S)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We've all typed a long command, hit Enter, and received a &lt;code&gt;Permission denied&lt;/code&gt; error. Instead of retyping or reaching for &lt;code&gt;sudo !!&lt;/code&gt;, this widget toggles &lt;code&gt;sudo&lt;/code&gt; on and off at the front of whatever is currently in your buffer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo-command-line() {&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;[[ -z $BUFFER ]] &amp;amp;&amp;amp; zle up-history&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;if [[ $BUFFER == sudo\ * ]]; then&lt;/code&gt;&lt;br&gt;&lt;br&gt;
        &lt;code&gt;LBUFFER="${LBUFFER#sudo }"&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;else&lt;/code&gt;&lt;br&gt;&lt;br&gt;
        &lt;code&gt;LBUFFER="sudo $LBUFFER"&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;fi&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;}&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zle -N sudo-command-line&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;bindkey '\es' sudo-command-line&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LBUFFER&lt;/code&gt; is everything to the left of the cursor. By prepending or stripping &lt;code&gt;sudo&lt;/code&gt; from it, the widget works correctly regardless of where your cursor is in the line.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Smart Clear and List (Ctrl+K)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The standard &lt;code&gt;Ctrl+L&lt;/code&gt; clears the screen. The Maruti-Zsh version wipes the screen and immediately runs &lt;code&gt;ls&lt;/code&gt; so you always have immediate context about your working directory. It's OS-aware — &lt;code&gt;-G&lt;/code&gt; for macOS, &lt;code&gt;--color=auto&lt;/code&gt; for Linux:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clear-ls-widget() {&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;clear&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;if [[ "$OSTYPE" == "darwin"* ]]; then&lt;/code&gt;&lt;br&gt;&lt;br&gt;
        &lt;code&gt;ls -pG&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;else&lt;/code&gt;&lt;br&gt;&lt;br&gt;
        &lt;code&gt;ls -p --color=auto&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;fi&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;zle redisplay&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;}&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zle -N clear-ls-widget&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;bindkey '^K' clear-ls-widget&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zle redisplay&lt;/code&gt; tells the line editor to re-render the prompt at the bottom after the &lt;code&gt;ls&lt;/code&gt; output, so your cursor is always in the right place.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Inline Search Pipes (Alt+G)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When parsing logs or command output, you end up typing &lt;code&gt;| grep&lt;/code&gt; constantly. This widget appends it to your buffer instantly and positions the cursor ready to type the search term:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wrap-grep() {&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;BUFFER="$BUFFER | grep "&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;CURSOR=$#BUFFER&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;}&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zle -N wrap-grep&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;bindkey '\eg' wrap-grep&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Edit Command in $EDITOR (Ctrl+O)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When a one-liner gets too complex to manage on a single line, &lt;code&gt;Ctrl+O&lt;/code&gt; drops the entire buffer into your &lt;code&gt;$EDITOR&lt;/code&gt; (vim, nano, or whatever you have set). Save and quit, and the command executes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;autoload -Uz edit-command-line&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;zle -N edit-command-line&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;bindkey '^O' edit-command-line&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. History Prefix Search (Arrow Keys)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This one is simple but transforms how you use history. Bound to the up and down arrow keys, it searches history by whatever prefix you've already typed — so if you type &lt;code&gt;git&lt;/code&gt; and press up, you only cycle through previous &lt;code&gt;git&lt;/code&gt; commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bindkey '^[[A' up-line-or-search&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;bindkey '^[[B' down-line-or-search&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Full Keybinding Reference&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Binding&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Alt+←&lt;/code&gt; / &lt;code&gt;Alt+→&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Move backward / forward one word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to beginning of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to end of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;↑&lt;/code&gt; / &lt;code&gt;↓&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;History search by prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Insert last argument from previous command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Backspace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete previous word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+Delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete next word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+U&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear entire command buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kill from cursor to end of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Toggle &lt;code&gt;sudo&lt;/code&gt; on current line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Alt+G&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Append `&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;{% raw %}&lt;code&gt;Ctrl+K&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Clear screen + show directory listing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+O&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open current command in &lt;code&gt;$EDITOR&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+X N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to &lt;code&gt;/etc/nginx/sites-available/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+X W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Jump to &lt;code&gt;/var/www/html/&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Making It a Reusable Product&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the environment was rock-solid, I packaged it into a clean open-source project with a dedicated installer and uninstaller.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;install.sh&lt;/code&gt; script handles orchestration safely. It expects you to have &lt;code&gt;zsh&lt;/code&gt; installed, verifies its presence, and fails fast if it's missing:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if ! command -v zsh &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;echo "❌ Error: Zsh is not installed on this system."&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;echo "Please install Zsh using your system's package manager first."&lt;/code&gt;&lt;br&gt;&lt;br&gt;
    &lt;code&gt;exit 1&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;fi&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From there it sets up the directory structure, clones Powerlevel10k and the two plugins (&lt;code&gt;zsh-autosuggestions&lt;/code&gt; and &lt;code&gt;zsh-syntax-highlighting&lt;/code&gt;) using &lt;code&gt;--depth=1&lt;/code&gt; shallow clones to stay lean, handles font installation with OS-aware paths (&lt;code&gt;~/Library/Fonts&lt;/code&gt; on macOS, &lt;code&gt;~/.fonts&lt;/code&gt; on Linux), and drops you into a fresh session.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;uninstall.sh&lt;/code&gt; is a complete cleanup utility — it removes the configuration directory, Powerlevel10k, and the &lt;code&gt;.zshrc&lt;/code&gt;, then restores either the system default from &lt;code&gt;/etc/skel/.zshrc&lt;/code&gt; or creates a clean empty one if no system default exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Extending It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The modular loader means adding your own configuration is trivial. Drop any &lt;code&gt;.zsh&lt;/code&gt; file into &lt;code&gt;~/.maruti-zsh/.zsh/&lt;/code&gt; and it will be sourced automatically on the next shell start — no edits to &lt;code&gt;.zshrc&lt;/code&gt; required:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Example: add your own work-specific aliases&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;nano ~/.maruti-zsh/.zsh/work-aliases.zsh&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Dropping Oh-My-Zsh forced me to actually understand the tool I spend hours inside every single day. My terminal prompt now renders instantly, my memory footprint is negligible, and every line of my shell configuration is something I wrote and fully understand.&lt;/p&gt;

&lt;p&gt;The circular sourcing bug that locked me out of my own server was the best thing that could have happened — it forced me to understand how Zsh glob patterns and stack memory interact, and the resulting design is tighter for it.&lt;/p&gt;

&lt;p&gt;If you want to try it, modify it, or use it to breathe new life into an older machine, the repository is fully documented and open for customization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mukuld/maruti-zsh" rel="noopener noreferrer"&gt;&lt;strong&gt;github.com/mukuld/maruti-zsh&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>zsh</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Automatic VPN Connectivity for Entra ID-Only Windows Devices: A Native Approach</title>
      <dc:creator>Mukul Dharwadkar</dc:creator>
      <pubDate>Mon, 28 Apr 2025 22:08:32 +0000</pubDate>
      <link>https://dev.to/mukul_d/manipulating-image-position-using-cssddd-4kb9</link>
      <guid>https://dev.to/mukul_d/manipulating-image-position-using-cssddd-4kb9</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;As enterprises shift towards cloud-first models, traditional on-premises VPN solutions like Always-On VPN Device Tunnels are no longer compatible with Entra ID-only (formerly Azure AD-only) Windows devices. &lt;/p&gt;

&lt;p&gt;This post presents a simple, robust framework using built-in Windows features — &lt;strong&gt;PowerShell&lt;/strong&gt;, &lt;strong&gt;VBScript&lt;/strong&gt;, and &lt;strong&gt;Task Scheduler&lt;/strong&gt; — to automate and ensure reliable VPN connectivity without third-party software.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Modern IT emphasizes mobility, zero-trust architectures, and identity-driven access. Entra ID-only joined devices support this model by eliminating traditional domain dependencies.&lt;/p&gt;

&lt;p&gt;However, VPN connectivity has lagged behind. Microsoft's Always-On VPN Device Tunnel requires domain-joined devices, making it unsuitable for Entra ID-only setups.&lt;/p&gt;

&lt;p&gt;Organizations need a flexible, lightweight alternative that fits cloud-native deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Without automatic VPN tunnels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users must manually start VPN connections.&lt;/li&gt;
&lt;li&gt;Access to internal resources is inconsistent.&lt;/li&gt;
&lt;li&gt;IT support tickets increase.&lt;/li&gt;
&lt;li&gt;Security risks grow due to dependency on user actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The goal:&lt;/strong&gt;  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Seamless, persistent VPN connectivity using only native Windows tools.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  My Native Solution
&lt;/h2&gt;

&lt;p&gt;This lightweight solution uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Batch Script&lt;/strong&gt; to install components.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;PowerShell Script&lt;/strong&gt; to start and monitor VPN.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;VBScript Wrapper&lt;/strong&gt; to run PowerShell invisibly.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Task Scheduler Job&lt;/strong&gt; to automate at startup and login.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components Breakdown
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Installation Batch Script&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a hidden folder &lt;code&gt;C:\Scripts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copies operational scripts.&lt;/li&gt;
&lt;li&gt;Triggers task creation using PowerShell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. PowerShell VPN Script&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks VPN connection status.&lt;/li&gt;
&lt;li&gt;Starts VPN if not connected.&lt;/li&gt;
&lt;li&gt;Monitors and retries using an exponential backoff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. VBScript Wrapper&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launches PowerShell script silently.&lt;/li&gt;
&lt;li&gt;No flashing command windows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Task Scheduler Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launches the VBScript on system startup and login.&lt;/li&gt;
&lt;li&gt;Runs under SYSTEM privileges for resilience.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Technical Workflow
&lt;/h2&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyaavlrxcdmb2slzen56y.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%2Fyaavlrxcdmb2slzen56y.png" alt="Image description" width="689" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Device boots.&lt;/li&gt;
&lt;li&gt;Task Scheduler triggers VBScript.&lt;/li&gt;
&lt;li&gt;VBScript runs PowerShell invisibly.&lt;/li&gt;
&lt;li&gt;PowerShell checks VPN status:

&lt;ul&gt;
&lt;li&gt;If connected, exit.&lt;/li&gt;
&lt;li&gt;If not connected, attempt connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monitor until connected.&lt;/li&gt;
&lt;li&gt;Log results to Event Viewer.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How to Deploy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Package the &lt;code&gt;.bat&lt;/code&gt;, &lt;code&gt;.ps1&lt;/code&gt;, &lt;code&gt;.vbs&lt;/code&gt; scripts together.&lt;/li&gt;
&lt;li&gt;Run the batch file as an &lt;strong&gt;administrator&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Verify that the scheduled task is created.&lt;/li&gt;
&lt;li&gt;Reboot and test VPN auto-connectivity.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  This can also be deployed through Intune which is what I have done. Reach out if you need help or guidance.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tasks run as &lt;strong&gt;SYSTEM&lt;/strong&gt; — no user credentials exposed.&lt;/li&gt;
&lt;li&gt;Scripts are hidden in system folders.&lt;/li&gt;
&lt;li&gt;Future enhancements could include &lt;strong&gt;script signing&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Business Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduced IT workload:&lt;/strong&gt; VPN management becomes hands-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better user experience:&lt;/strong&gt; No manual connection steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports cloud-first initiatives:&lt;/strong&gt; No legacy dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved security:&lt;/strong&gt; Consistent access control.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Package VPN management as a &lt;strong&gt;Windows Service&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Expand support for &lt;strong&gt;macOS&lt;/strong&gt; and &lt;strong&gt;Linux&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Collaborate with Microsoft for Entra ID device tunnel support.&lt;/li&gt;
&lt;li&gt;Add modern authentication integration (e.g., Azure MFA).&lt;/li&gt;
&lt;li&gt;Build user-facing tray apps for VPN status visibility.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;VPN connectivity for Entra ID-only devices should be &lt;strong&gt;automatic, reliable, and invisible&lt;/strong&gt; to users.&lt;/p&gt;

&lt;p&gt;By leveraging native Windows scripting and scheduling tools, organizations can bridge today's VPN gap without sacrificing their cloud-first strategy.&lt;/p&gt;

&lt;p&gt;This lightweight framework is the first step towards a future of &lt;strong&gt;OS-agnostic, automated VPN management&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Script List:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;installVPNAutomation.bat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startAndCheckVPN.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startAndCheckVPN.vbs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startAndCheckVPNTasks.ps1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page" rel="noopener noreferrer"&gt;Task Scheduler Security Context&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-event-logs" rel="noopener noreferrer"&gt;PowerShell Event Logging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/devices/azuread-joined-devices-overview" rel="noopener noreferrer"&gt;Entra ID-Only Device Join Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Mukul Dharwadkar&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Copyright:&lt;/strong&gt; JnanaTech Ventures (2025)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;No unauthorized copying or distribution permitted.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>alwaysonvpn</category>
      <category>entraid</category>
      <category>cybersecurity</category>
      <category>vpn</category>
    </item>
    <item>
      <title>Manipulating image position using CSS</title>
      <dc:creator>Mukul Dharwadkar</dc:creator>
      <pubDate>Tue, 01 Oct 2024 22:14:00 +0000</pubDate>
      <link>https://dev.to/mukul_d/manipulating-image-position-using-css-1obd</link>
      <guid>https://dev.to/mukul_d/manipulating-image-position-using-css-1obd</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Centering an image alongside text in a container might seem straightforward, but dealing with the differences between block and inline elements often complicates the task. While it's easy to position these elements separately, combining them can present unexpected challenges. I recently encountered this issue when trying to align an image to the left and center the heading within the same container. After experimenting with outdated techniques like float, I turned to modern CSS tools like Flexbox to find a cleaner, more efficient solution. This article explores my approach and the lessons learned along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The challenge
&lt;/h3&gt;

&lt;p&gt;The text I was using was my &lt;a href="https://www.dharwadkar.com/NewSite" rel="noopener noreferrer"&gt;site&lt;/a&gt; heading with the HTML element &lt;code&gt;&amp;lt;h1.&lt;/code&gt; which is a block-level element, which means that it doesn't play nice with other elements on the same line. It starts on a new line and needs to take up the entire line. Whereas the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; element is an inline element which means it is happy to play with others on the same line. Of course, I could play with the CSS &lt;code&gt;float&lt;/code&gt; property, but there is a problem. The moment I introduce &lt;code&gt;float&lt;/code&gt; for an element, it takes that element out of the document flow. and it becomes hard to control the behavior of that element.&lt;/p&gt;

&lt;h3&gt;
  
  
  Options
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, I tried using &lt;code&gt;float: left&lt;/code&gt; and &lt;code&gt;float: inline-start&lt;/code&gt;, but it doesn't always behave as I want. As a best practice, I try to use the latest techniques as much as possible and that's where the modern &lt;code&gt;flex&lt;/code&gt; and CSS GridBox came in. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex" rel="noopener noreferrer"&gt;Flexbox&lt;/a&gt; when assigned to the parent container, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/align-content" rel="noopener noreferrer"&gt;aligns&lt;/a&gt; all the content text to the left as shown below.&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%2Fak6qwlnv2oa77lx4lbby.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%2Fak6qwlnv2oa77lx4lbby.png" alt="Initial result with just flex"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a lot of trial and error, it came down to using specificity and going minimalist. I also wanted to have the option to style images that I might use on the site independently so I didn't apply any styling to the core &lt;code&gt;img&lt;/code&gt; element. I created several classes to manipulate the images and applied those. During all this trial and error, another problem vexed me. I couldn't get the image to align to the middle of the parent container with all the techniques I knew. I researched and tried with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/align-self" rel="noopener noreferrer"&gt;&lt;code&gt;align-self&lt;/code&gt;&lt;/a&gt; property. That finally worked. I didn't want to apply this to the core &lt;code&gt;img&lt;/code&gt; element and I didn't want to create a class for this so I used the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Combinators#child_combinator" rel="noopener noreferrer"&gt;Child Combinator&lt;/a&gt; to target the specific &lt;code&gt;img&lt;/code&gt; element which is a child of &lt;code&gt;header&lt;/code&gt; element (&lt;code&gt;header &amp;gt; img&lt;/code&gt;). That took care of the issue of image alignment.&lt;/p&gt;

&lt;p&gt;The next issue was to align the header text in the center. I tried all the tricks I knew with &lt;code&gt;text-align&lt;/code&gt;, &lt;code&gt;align-self&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt;, &lt;code&gt;justify-self&lt;/code&gt;, and &lt;code&gt;justify-items&lt;/code&gt;. But because the parent &lt;code&gt;header&lt;/code&gt; element was marked as &lt;code&gt;flex&lt;/code&gt;, the subsequent styles didn't apply. Finally I tried a simple trick to center the content using &lt;code&gt;margin: auto&lt;/code&gt; and that did the trick. Here's how the final output looks now.&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%2F2uy69gsaomgbfry98by0.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%2F2uy69gsaomgbfry98by0.png" alt="Final result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even when I change the height of the header container, the image and text are vertically in the middle of the element and stay where there on the x-axis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final code
&lt;/h3&gt;

&lt;p&gt;HTML code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;header class="flexi"&amp;gt;
  &amp;lt;img class="round-img small" src="img/Mukul-2019.jpg" alt="Mukul Dharwadkar" caption="Picture of Mukul Dharwadkar" /&amp;gt;
  &amp;lt;h1 class="center-align"&amp;gt;
    Mukul Dharwadkar
  &amp;lt;/h1&amp;gt;
&amp;lt;/header&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header {
    width: 900px;
    margin: auto;
    height: 120px;
    background-color: antiquewhite;
  }

/* The CSS rule below is highly specific for an img element that is a child of the header element.
Typically there will be only one img element inside the header and therefore this is safe to keep */

header &amp;gt; img {
  align-self: center;
}

.flexi {
  display: flex;
}

.round-img {
  border-radius: 50%;
}

.small {
  width: 100px;
}

.flexi {
  display: flex;
}

.center-align {
  margin: auto;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full code is on my &lt;a href="https://github.com/mukuld/NewSite.git" rel="noopener noreferrer"&gt;Github&lt;/a&gt; repo. Feel free to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion:
&lt;/h3&gt;

&lt;p&gt;Achieving the perfect alignment of images and text in web design often requires experimenting with different CSS techniques. In this case, Flexbox proved to be the most efficient and modern solution for centering content within a container, while maintaining the flexibility to adjust styling independently. By using targeted selectors like the Child Combinator and leveraging Flexbox’s alignment properties, I was able to solve the issue cleanly and efficiently. This method not only streamlines the code but also ensures that future adjustments will be easier to manage. CSS can be tricky, but with the right approach, you can create polished, professional layouts.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>html</category>
    </item>
  </channel>
</rss>
