Multibyte strings7 min readExamples run on PHP 8.5.10

mb_strwidth()

Learn how mb_strwidth() measures the display width of a string, counting halfwidth characters as 1 and fullwidth characters as 2, with examples and pitfalls.

Signature

mb_strwidth(string $string, ?string $encoding = null): int
Available since PHP 4.0.67.48.08.18.28.38.48.5All versions →

Purpose

mb_strwidth() estimates how many terminal-style columns a string occupies. Each halfwidth character counts as 1 and each fullwidth character counts as 2. Fullwidth characters include most CJK ideographs, Japanese kana, Korean Hangul syllables and the fullwidth Latin forms. Use it when layout depends on visual columns rather than on bytes or characters, for example in monospaced text tables, console output or fixed-width previews.

Signature

Parameters and defaults

  • $string is the text to measure.
  • $encoding names the character encoding of $string, such as 'UTF-8'. If you omit it or pass null, PHP uses the current internal encoding. Passing the encoding explicitly keeps the result independent of global settings.

Return value and failures

The function returns an int: the total width of the string. An empty string has width 0.

Since PHP 8.0.0, an invalid $encoding name throws a ValueError. Before 8.0.0 it emitted a warning instead. Also since 8.0.0, $encoding is nullable.

The manual defines width by a fixed list of code point ranges. Characters inside those ranges, such as U+3000-U+303E, U+3041-U+3096, U+4E00-U+A48C, U+AC00-U+D7A3 and U+FF01-U+FF60, count as 2. Every other character counts as 1.

Example 1: halfwidth and fullwidth characters

The Latin letter a has width 1. Its fullwidth counterpart U+FF41 has width 2. Hiragana characters are fullwidth, so five of them measure 10.

Example
<?php
echo json_encode([
    mb_strwidth('a', 'UTF-8'),
    mb_strwidth("\u{ff41}", 'UTF-8'),
    mb_strwidth('Hello', 'UTF-8'),
    mb_strwidth('こんにちは', 'UTF-8'),
]);
Output
[1,2,5,10]

Example 2: bytes, characters and width are different

Three measurements of the same string answer three different questions. strlen() counts bytes, mb_strlen() counts characters and mb_strwidth() counts display columns.

Example
<?php
$text = '日本語';
echo json_encode([
    'bytes' => strlen($text),
    'chars' => mb_strlen($text, 'UTF-8'),
    'width' => mb_strwidth($text, 'UTF-8'),
]);
Output
{"bytes":9,"chars":3,"width":6}

Example 3: padding to a column width

str_pad() counts bytes, so it misaligns multibyte text. This helper pads by display width instead. max(0, ...) stops it from using a negative repeat count when the text is already too wide.

Example
<?php
function padToWidth(string $text, int $width): string
{
    $missing = max(0, $width - mb_strwidth($text, 'UTF-8'));
    return $text . str_repeat('.', $missing);
}

$rows = ['cat', '犬', 'ねこ'];
echo json_encode(
    array_map(fn(string $row): string => padToWidth($row, 6), $rows),
    JSON_UNESCAPED_UNICODE
);
Output
["cat...","犬....","ねこ.."]

Edge cases

  • Empty string: the result is 0.
  • Mixed text: widths are summed, so abc plus two fullwidth characters gives 3 + 4 = 7.
  • Combining marks and emoji: the function follows the manual's range table and nothing more. A character outside the listed ranges counts as 1, even if a terminal or font draws it wider or narrower. A combining accent such as U+0301 is not in the fullwidth list, so it adds 1 to the total.
  • Truncated results: the function measures only. To cut a string to a column budget, look at mb_strimwidth().
  • Invalid encoding names: mb_strwidth('a', 'NOPE') throws a ValueError, so catch it if the encoding comes from untrusted input.

Common mistakes

  1. Using strlen() or str_pad() for alignment. These count bytes. A three-character Japanese word occupies nine bytes in UTF-8 but six columns.
  2. Using mb_strlen() and expecting columns. It counts characters, so a fullwidth character still counts once.
  3. Treating the result as exact rendering width. It is a table-based approximation. Fonts, terminals and emoji sequences may differ.
  4. Relying on the default encoding. Pass 'UTF-8' explicitly when your data is UTF-8, so changes to the internal encoding cannot alter the answer.
  5. Assuming an error return. Modern PHP throws a ValueError for a bad encoding, so checking for false does nothing.
  • mb_strimwidth() truncates a string to a given width.
  • mb_strlen() counts characters.
  • strlen() counts bytes.
  • mb_internal_encoding() gets or sets the default encoding used when $encoding is null.

Recap

mb_strwidth() returns the number of display columns a string uses, counting fullwidth characters as 2 and all others as 1. It differs from byte and character counts, accepts an optional encoding, and throws a ValueError for an invalid encoding. Use it for column-based layout, and remember that it is an approximation based on a fixed range table.

Tested examples

Halfwidth and fullwidth characters
<?php
echo json_encode([
    mb_strwidth('a', 'UTF-8'),
    mb_strwidth("\u{ff41}", 'UTF-8'),
    mb_strwidth('Hello', 'UTF-8'),
    mb_strwidth('こんにちは', 'UTF-8'),
]);
Output
[1,2,5,10]
Bytes, characters and width compared
<?php
$text = '日本語';
echo json_encode([
    'bytes' => strlen($text),
    'chars' => mb_strlen($text, 'UTF-8'),
    'width' => mb_strwidth($text, 'UTF-8'),
]);
Output
{"bytes":9,"chars":3,"width":6}
Padding to a column width
<?php
function padToWidth(string $text, int $width): string
{
    $missing = max(0, $width - mb_strwidth($text, 'UTF-8'));
    return $text . str_repeat('.', $missing);
}

$rows = ['cat', '犬', 'ねこ'];
echo json_encode(
    array_map(fn(string $row): string => padToWidth($row, 6), $rows),
    JSON_UNESCAPED_UNICODE
);
Output
["cat...","犬....","ねこ.."]