Detailed changes
  
  
    
    @@ -624,6 +624,10 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
 				switch msg.Mode {
 				case ansi.AltScreenSaveCursorMode:
 					p.renderer.enterAltScreen()
+					// Main and alternate screen have their own Kitty keyboard
+					// stack. We need to request keyboard enhancements again
+					// when entering/exiting the alternate screen.
+					p.requestKeyboardEnhancements()
 				case ansi.TextCursorEnableMode:
 					p.renderer.showCursor()
 				case ansi.GraphemeClusteringMode:
@@ -645,6 +649,10 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
 				switch msg.Mode {
 				case ansi.AltScreenSaveCursorMode:
 					p.renderer.exitAltScreen()
+					// Main and alternate screen have their own Kitty keyboard
+					// stack. We need to request keyboard enhancements again
+					// when entering/exiting the alternate screen.
+					p.requestKeyboardEnhancements()
 				case ansi.TextCursorEnableMode:
 					p.renderer.hideCursor()
 				default:
@@ -1410,13 +1418,16 @@ func (p *Program) stopRenderer(kill bool) {
 // requestKeyboardEnhancements tries to enable keyboard enhancements and read
 // the active keyboard enhancements from the terminal.
 func (p *Program) requestKeyboardEnhancements() {
+	// XXX: We write to the renderer directly so that we synchronize with the
+	// alt-screen state of the renderer. This is because the main screen and
+	// alternate screen have their own Kitty keyboard state stack.
 	if p.requestedEnhancements.modifyOtherKeys > 0 {
-		p.execute(ansi.KeyModifierOptions(4, p.requestedEnhancements.modifyOtherKeys)) //nolint:mnd
-		p.execute(ansi.QueryModifyOtherKeys)
+		_, _ = p.renderer.writeString(ansi.KeyModifierOptions(4, p.requestedEnhancements.modifyOtherKeys)) //nolint:mnd
+		_, _ = p.renderer.writeString(ansi.QueryModifyOtherKeys)
 	}
 	if p.requestedEnhancements.kittyFlags > 0 {
-		p.execute(ansi.PushKittyKeyboard(p.requestedEnhancements.kittyFlags))
-		p.execute(ansi.RequestKittyKeyboard)
+		_, _ = p.renderer.writeString(ansi.PushKittyKeyboard(p.requestedEnhancements.kittyFlags))
+		_, _ = p.renderer.writeString(ansi.RequestKittyKeyboard)
 	}
 }
 
  
  
  
    
    @@ -135,6 +135,16 @@ func (s Style) GetPaddingLeft() int {
 	return s.getAsInt(paddingLeftKey)
 }
 
+// GetPaddingChar returns the style's padding character. If no value is set a
+// space (`\u0020`) is returned.
+func (s Style) GetPaddingChar() rune {
+	char := s.getAsRune(paddingCharKey)
+	if char == 0 {
+		return ' '
+	}
+	return char
+}
+
 // GetHorizontalPadding returns the style's left and right padding. Unset
 // values are measured as 0.
 func (s Style) GetHorizontalPadding() int {
@@ -186,6 +196,16 @@ func (s Style) GetMarginLeft() int {
 	return s.getAsInt(marginLeftKey)
 }
 
+// GetMarginChar returns the style's padding character. If no value is set a
+// space (`\u0020`) is returned.
+func (s Style) GetMarginChar() rune {
+	char := s.getAsRune(marginCharKey)
+	if char == 0 {
+		return ' '
+	}
+	return char
+}
+
 // GetHorizontalMargins returns the style's left and right margins. Unset
 // values are measured as 0.
 func (s Style) GetHorizontalMargins() int {
@@ -432,6 +452,19 @@ func (s Style) isSet(k propKey) bool {
 	return s.props.has(k)
 }
 
+func (s Style) getAsRune(k propKey) rune {
+	if !s.isSet(k) {
+		return 0
+	}
+	switch k { //nolint:exhaustive
+	case paddingCharKey:
+		return s.paddingChar
+	case marginCharKey:
+		return s.marginChar
+	}
+	return 0
+}
+
 func (s Style) getAsBool(k propKey, defaultVal bool) bool {
 	if !s.isSet(k) {
 		return defaultVal
  
  
  
    
    @@ -29,6 +29,8 @@ func (s *Style) set(key propKey, value any) {
 		s.paddingBottom = max(0, value.(int))
 	case paddingLeftKey:
 		s.paddingLeft = max(0, value.(int))
+	case paddingCharKey:
+		s.paddingChar = value.(rune)
 	case marginTopKey:
 		s.marginTop = max(0, value.(int))
 	case marginRightKey:
@@ -39,6 +41,8 @@ func (s *Style) set(key propKey, value any) {
 		s.marginLeft = max(0, value.(int))
 	case marginBackgroundKey:
 		s.marginBgColor = colorOrNil(value)
+	case marginCharKey:
+		s.marginChar = value.(rune)
 	case borderStyleKey:
 		s.borderStyle = value.(Border)
 	case borderTopForegroundKey:
@@ -111,6 +115,8 @@ func (s *Style) setFrom(key propKey, i Style) {
 		s.set(paddingBottomKey, i.paddingBottom)
 	case paddingLeftKey:
 		s.set(paddingLeftKey, i.paddingLeft)
+	case paddingCharKey:
+		s.set(paddingCharKey, i.paddingChar)
 	case marginTopKey:
 		s.set(marginTopKey, i.marginTop)
 	case marginRightKey:
@@ -121,6 +127,8 @@ func (s *Style) setFrom(key propKey, i Style) {
 		s.set(marginLeftKey, i.marginLeft)
 	case marginBackgroundKey:
 		s.set(marginBackgroundKey, i.marginBgColor)
+	case marginCharKey:
+		s.set(marginCharKey, i.marginChar)
 	case borderStyleKey:
 		s.set(borderStyleKey, i.borderStyle)
 	case borderTopForegroundKey:
@@ -320,6 +328,18 @@ func (s Style) PaddingBottom(i int) Style {
 	return s
 }
 
+// PaddingChar sets the character used for padding. This is useful for
+// rendering blocks with a specific character, such as a space or a dot.
+// Example of using [NBSP] as padding to prevent line breaks:
+//
+//	```go
+//	s := lipgloss.NewStyle().PaddingChar(lipgloss.NBSP)
+//	```
+func (s Style) PaddingChar(r rune) Style {
+	s.set(paddingCharKey, r)
+	return s
+}
+
 // ColorWhitespace determines whether or not the background color should be
 // applied to the padding. This is true by default as it's more than likely the
 // desired and expected behavior, but it can be disabled for certain graphic
@@ -390,6 +410,13 @@ func (s Style) MarginBackground(c color.Color) Style {
 	return s
 }
 
+// MarginChar sets the character used for the margin. This is useful for
+// rendering blocks with a specific character, such as a space or a dot.
+func (s Style) MarginChar(r rune) Style {
+	s.set(marginCharKey, r)
+	return s
+}
+
 // Border is shorthand for setting the border style and which sides should
 // have a border at once. The variadic argument sides works as follows:
 //
  
  
  
    
    @@ -10,7 +10,8 @@ import (
 )
 
 const (
-	nbsp            = '\u00A0'
+	// NBSP is the non-breaking space rune.
+	NBSP            = '\u00A0'
 	tabWidthDefault = 4
 )
 
@@ -44,6 +45,7 @@ const (
 	paddingRightKey
 	paddingBottomKey
 	paddingLeftKey
+	paddingCharKey
 
 	// Margins.
 	marginTopKey
@@ -51,6 +53,7 @@ const (
 	marginBottomKey
 	marginLeftKey
 	marginBackgroundKey
+	marginCharKey
 
 	// Border runes.
 	borderStyleKey
@@ -128,12 +131,14 @@ type Style struct {
 	paddingRight  int
 	paddingBottom int
 	paddingLeft   int
+	paddingChar   rune
 
 	marginTop     int
 	marginRight   int
 	marginBottom  int
 	marginLeft    int
 	marginBgColor color.Color
+	marginChar    rune
 
 	borderStyle         Border
 	borderTopFgColor    color.Color
@@ -387,23 +392,24 @@ func (s Style) Render(strs ...string) string {
 
 	// Padding
 	if !inline { //nolint:nestif
+		padChar := s.paddingChar
+		if padChar == 0 {
+			padChar = ' '
+		}
 		if leftPadding > 0 {
 			var st *ansi.Style
 			if colorWhitespace || styleWhitespace {
 				st = &teWhitespace
 			}
-			str = padLeft(str, leftPadding, st, nbsp)
+			str = padLeft(str, leftPadding, st, padChar)
 		}
 
-		// XXX: We use a non-breaking space to pad so that the padding is
-		// preserved when the string is copied and pasted.
-
 		if rightPadding > 0 {
 			var st *ansi.Style
 			if colorWhitespace || styleWhitespace {
 				st = &teWhitespace
 			}
-			str = padRight(str, rightPadding, st, nbsp)
+			str = padRight(str, rightPadding, st, padChar)
 		}
 
 		if topPadding > 0 {
@@ -494,8 +500,12 @@ func (s Style) applyMargins(str string, inline bool) string {
 	}
 
 	// Add left and right margin
-	str = padLeft(str, leftMargin, &style, ' ')
-	str = padRight(str, rightMargin, &style, ' ')
+	marginChar := s.marginChar
+	if marginChar == 0 {
+		marginChar = ' '
+	}
+	str = padLeft(str, leftMargin, &style, marginChar)
+	str = padRight(str, rightMargin, &style, marginChar)
 
 	// Top/bottom margin
 	if !inline {
  
  
  
    
    @@ -96,6 +96,13 @@ func (s Style) UnsetPadding() Style {
 	s.unset(paddingRightKey)
 	s.unset(paddingTopKey)
 	s.unset(paddingBottomKey)
+	s.unset(paddingCharKey)
+	return s
+}
+
+// UnsetPaddingChar removes the padding character style rule, if set.
+func (s Style) UnsetPaddingChar() Style {
+	s.unset(paddingCharKey)
 	return s
 }
 
  
  
  
    
    @@ -1,109 +0,0 @@
-// Package screen provides functions and helpers to manipulate a [uv.Screen].
-package screen
-
-import uv "github.com/charmbracelet/ultraviolet"
-
-// Clear clears the screen with empty cells. This is equivalent to filling the
-// screen with empty cells.
-//
-// If the screen implements a [Clear] method, it will be called instead of
-// filling the screen with empty cells.
-func Clear(scr uv.Screen) {
-	if c, ok := scr.(interface {
-		Clear()
-	}); ok {
-		c.Clear()
-		return
-	}
-	Fill(scr, nil)
-}
-
-// ClearArea clears the given area of the screen with empty cells. This is
-// equivalent to filling the area with empty cells.
-//
-// If the screen implements a [ClearArea] method, it will be called instead of
-// filling the area with empty cells.
-func ClearArea(scr uv.Screen, area uv.Rectangle) {
-	if c, ok := scr.(interface {
-		ClearArea(area uv.Rectangle)
-	}); ok {
-		c.ClearArea(area)
-		return
-	}
-	FillArea(scr, nil, area)
-}
-
-// Fill fills the screen with the given cell. If the cell is nil, it fills the
-// screen with empty cells.
-//
-// If the screen implements a [Fill] method, it will be called instead of
-// filling the screen with empty cells.
-func Fill(scr uv.Screen, cell *uv.Cell) {
-	if f, ok := scr.(interface {
-		Fill(cell *uv.Cell)
-	}); ok {
-		f.Fill(cell)
-		return
-	}
-	FillArea(scr, cell, scr.Bounds())
-}
-
-// FillArea fills the given area of the screen with the given cell. If the cell
-// is nil, it fills the area with empty cells.
-//
-// If the screen implements a [FillArea] method, it will be called instead of
-// filling the area with empty cells.
-func FillArea(scr uv.Screen, cell *uv.Cell, area uv.Rectangle) {
-	if f, ok := scr.(interface {
-		FillArea(cell *uv.Cell, area uv.Rectangle)
-	}); ok {
-		f.FillArea(cell, area)
-		return
-	}
-	for y := area.Min.Y; y < area.Max.Y; y++ {
-		for x := area.Min.X; x < area.Max.X; x++ {
-			scr.SetCell(x, y, cell)
-		}
-	}
-}
-
-// CloneArea clones the given area of the screen and returns a new buffer
-// with the same size as the area. The new buffer will contain the same cells
-// as the area in the screen.
-// Use [uv.Buffer.Draw] to draw the cloned buffer to a screen again.
-//
-// If the screen implements a [CloneArea] method, it will be called instead of
-// cloning the area manually.
-func CloneArea(scr uv.Screen, area uv.Rectangle) *uv.Buffer {
-	if c, ok := scr.(interface {
-		CloneArea(area uv.Rectangle) *uv.Buffer
-	}); ok {
-		return c.CloneArea(area)
-	}
-	buf := uv.NewBuffer(area.Dx(), area.Dy())
-	for y := area.Min.Y; y < area.Max.Y; y++ {
-		for x := area.Min.X; x < area.Max.X; x++ {
-			cell := scr.CellAt(x, y)
-			if cell == nil || cell.IsZero() {
-				continue
-			}
-			buf.SetCell(x-area.Min.X, y-area.Min.Y, cell.Clone())
-		}
-	}
-	return buf
-}
-
-// Clone creates a new [uv.Buffer] clone of the given screen. The new buffer will
-// have the same size as the screen and will contain the same cells.
-// Use [uv.Buffer.Draw] to draw the cloned buffer to a screen again.
-//
-// If the screen implements a [Clone] method, it will be called instead of
-// cloning the entire screen manually.
-func Clone(scr uv.Screen) *uv.Buffer {
-	if c, ok := scr.(interface {
-		Clone() *uv.Buffer
-	}); ok {
-		return c.Clone()
-	}
-	return CloneArea(scr, scr.Bounds())
-}
  
  
  
    
    @@ -254,8 +254,8 @@ github.com/charmbracelet/bubbles/v2/spinner
 github.com/charmbracelet/bubbles/v2/textarea
 github.com/charmbracelet/bubbles/v2/textinput
 github.com/charmbracelet/bubbles/v2/viewport
-# github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250708152737-144080f3d891
-## explicit; go 1.24.3
+# github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250710185017-3c0ffd25e595
+## explicit; go 1.24.0
 github.com/charmbracelet/bubbletea/v2
 # github.com/charmbracelet/colorprofile v0.3.1
 ## explicit; go 1.23.0
@@ -269,7 +269,7 @@ github.com/charmbracelet/glamour/v2
 github.com/charmbracelet/glamour/v2/ansi
 github.com/charmbracelet/glamour/v2/internal/autolink
 github.com/charmbracelet/glamour/v2/styles
-# github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250708152830-0fa4ef151093
+# github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250710185058-03664cb9cecb
 ## explicit; go 1.24.2
 github.com/charmbracelet/lipgloss/v2
 github.com/charmbracelet/lipgloss/v2/table
@@ -280,7 +280,6 @@ github.com/charmbracelet/log/v2
 # github.com/charmbracelet/ultraviolet v0.0.0-20250708152637-0fe0235c8db9
 ## explicit; go 1.24.0
 github.com/charmbracelet/ultraviolet
-github.com/charmbracelet/ultraviolet/screen
 # github.com/charmbracelet/x/ansi v0.9.3
 ## explicit; go 1.23.0
 github.com/charmbracelet/x/ansi
@@ -839,5 +838,5 @@ mvdan.cc/sh/v3/fileutil
 mvdan.cc/sh/v3/interp
 mvdan.cc/sh/v3/pattern
 mvdan.cc/sh/v3/syntax
-# github.com/charmbracelet/bubbletea/v2 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250708152737-144080f3d891
-# github.com/charmbracelet/lipgloss/v2 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250708152830-0fa4ef151093
+# github.com/charmbracelet/bubbletea/v2 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250710185017-3c0ffd25e595
+# github.com/charmbracelet/lipgloss/v2 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250710185058-03664cb9cecb