Keyboard Avoidance

키보드 어보이던스

When the software keyboard rises, the layout resizes or scrolls so the field being typed into never ends up hidden underneath it.

Also known as: KeyboardAvoidingView키보드 회피
···
html
<div class="stage"><div class="phone"><div class="notch"></div><div class="screen" id="screen">
<div class="kbcontent" id="kbcontent">
  <div class="msg left"></div><div class="msg right"></div><div class="msg left"></div>
</div>
<div class="inputrow" id="inputrow">
  <div class="inputfake" id="inputfake">Message...</div><div class="sendbtn">&#8593;</div>
</div>
<div class="keyboard" id="keyboard"></div></div><div class="home"></div></div></div>
css
.stage{position:absolute;inset:0;display:grid;place-items:center;overflow:hidden}
.phone{position:relative;height:90vmin;max-height:640px;aspect-ratio:9/19.5;background:#08080c;
  border-radius:20%/8.6%;box-shadow:0 0 0 2px rgba(255,255,255,.08) inset,0 12px 30px rgba(0,0,0,.35)}
.phone .notch{position:absolute;left:50%;top:0;transform:translateX(-50%);width:38%;height:5.6%;
  background:#08080c;border-radius:0 0 46% 46%;z-index:80}
.phone .screen{position:absolute;top:7.6%;left:3%;right:3%;bottom:3%;border-radius:11%/4.6%;overflow:hidden;
  background:var(--surface);color:var(--fg)}
.phone .home{position:absolute;left:50%;bottom:1.1%;transform:translateX(-50%);width:32%;height:3px;
  border-radius:2px;background:rgba(255,255,255,.45);z-index:80}
.pfx{position:absolute;left:0;top:0;width:16px;height:16px;margin:-8px 0 0 -8px;border-radius:50%;
  background:color-mix(in srgb,var(--accent) 55%,transparent);
  box-shadow:0 0 0 6px color-mix(in srgb,var(--accent) 15%,transparent);pointer-events:none;z-index:999;
  transition:opacity .2s}
.pfx.hold{width:30px;height:30px;margin:-15px 0 0 -15px;
  box-shadow:0 0 0 9px color-mix(in srgb,var(--accent) 20%,transparent);
  transition:width .5s linear,height .5s linear,margin .5s linear,box-shadow .5s linear,opacity .2s}

.kbcontent{position:absolute;top:0;left:0;right:0;bottom:16%;overflow:hidden;padding:6% 7%;
  display:grid;align-content:end;gap:5%;transition:bottom .3s cubic-bezier(.2,.8,.2,1)}
.msg{max-width:64%;height:clamp(10px,7vmin,18px);border-radius:10px;background:var(--line)}
.msg.right{justify-self:end;background:color-mix(in srgb,var(--accent) 30%,var(--line))}
.inputrow{position:absolute;left:0;right:0;bottom:3%;height:11%;display:flex;align-items:center;gap:4%;
  padding:0 6%;transition:bottom .3s cubic-bezier(.2,.8,.2,1);z-index:10}
.inputfake{flex:1;height:78%;border-radius:999px;border:1px solid var(--line);display:flex;align-items:center;
  padding:0 6%;font-size:clamp(6px,2.2vmin,8.5px);color:var(--muted)}
.sendbtn{width:16%;aspect-ratio:1;border-radius:50%;background:var(--accent);color:#fff;
  display:grid;place-items:center;font-size:clamp(7px,2.6vmin,10px);flex:none}
.keyboard{position:absolute;left:0;right:0;bottom:0;height:42%;background:var(--line);
  border-radius:8% 8% 0 0;transform:translateY(100%);transition:transform .3s cubic-bezier(.2,.8,.2,1);z-index:5}
.keyboard.show{transform:translateY(0)}
js
const pfx=document.createElement('div');pfx.className='pfx';document.body.appendChild(pfx);
const screenEl=document.getElementById('screen');
let pfxAuto=true;
function pfxHide(){pfx.style.opacity='0';}
addEventListener('pointerdown',()=>{pfxAuto=false;pfxHide();},{capture:true});
function pfxAt(fx,fy){
  const r=screenEl.getBoundingClientRect();
  const x=r.left+r.width*fx, y=r.top+r.height*fy;
  pfx.style.transform='translate('+x+'px,'+y+'px)';
  return {x,y};
}
function pfxWalk(x0,y0,x1,y1,ms){
  return new Promise(res=>{
    const start=performance.now();
    function step(now){
      if(!pfxAuto) return res();
      const p=Math.min(1,(now-start)/ms), e=1-Math.pow(1-p,3);
      pfxAt(x0+(x1-x0)*e, y0+(y1-y0)*e);
      if(p<1) requestAnimationFrame(step); else res();
    }
    requestAnimationFrame(step);
  });
}
function pfxWait(ms){
  return new Promise(res=>{
    const start=performance.now();
    function step(now){ if(!pfxAuto) return res(); if(now-start<ms) requestAnimationFrame(step); else res(); }
    requestAnimationFrame(step);
  });
}

const inputfake=document.getElementById('inputfake'), inputrow=document.getElementById('inputrow'),
  keyboard=document.getElementById('keyboard'), kbcontent=document.getElementById('kbcontent');
function setKb(show){
  keyboard.classList.toggle('show',show);
  inputrow.style.bottom= show?'45%':'3%';
  kbcontent.style.bottom= show?'58%':'16%';
}
inputfake.addEventListener('pointerdown',()=>{ pfxAuto=false; pfxHide(); setKb(true); });
kbcontent.addEventListener('pointerdown',()=>{ pfxAuto=false; pfxHide(); setKb(false); });
function fracOf(el){ const r=el.getBoundingClientRect(), sr=screenEl.getBoundingClientRect();
  return {x:(r.left+r.width/2-sr.left)/sr.width,y:(r.top+r.height/2-sr.top)/sr.height}; }
async function autoSeq(){
  while(pfxAuto){
    setKb(false);
    let f=fracOf(inputfake);
    await pfxWalk(f.x,f.y,f.x,f.y,400); if(!pfxAuto)break;
    setKb(true);
    await pfxWait(1100); if(!pfxAuto)break;
    await pfxWalk(f.x,f.y,0.5,0.3,300); if(!pfxAuto)break;
    setKb(false);
    await pfxWait(700);
  }
}
autoSeq();

On the web, a keyboard opening can be detected through the `visualViewport`'s `resize` event as its `height` shrinks. Native splits into two strategies — Android's `windowSoftInputMode="adjustResize"` redraws the layout to fit the remaining height (the content area shrinks), while `adjustPan` or React Native's `KeyboardAvoidingView` with `behavior="padding"` instead shifts the screen up so the focused field stays visible (the layout itself is untouched).

Either way, the principle is the same — never let the keyboard simply cover content; keep wherever you're typing floating above it. In a list screen, resizing the whole scrollable area (not just nudging the input) matters, so the rest of the conversation stays scrollable until the keyboard closes.

The usual accident happens elsewhere on screen — a bottom-pinned CTA or tab bar that isn't listening for the resize just sits underneath the keyboard, unreachable exactly when it's needed most.

When to use

Use it on any screen with a text input near the bottom — login, a chat composer, comments. Verify first on the shortest common device height, since that's where the keyboard leaves the least room.