Skip to main content
CalculatorField ← Back to Table of Contents

Summary

Numeric text input with a calculator trigger and a shared floating panel (one panel per page). Users can type directly or open the keypad to build expressions, preview results live, and Insert the computed value into the active field. Each CalculatorField keeps its own expression session — switch fields without closing the panel.
ClassBjanczak\FilamentFlexFields\Filament\Forms\Components\CalculatorField
State typeint|float|null when nullable()
Model cast'weight' => 'decimal:2' · 'quantity' => 'integer'
FieldType(no dedicated FieldType mapping yet — use the class directly)
Playgroundcalculator-field slug in Flex Fields playground
Default variantprimary
Default sizemd
Default rounding modetruncate
Works with all standard Filament field APIs: required(), disabled(), readOnly(), hidden(), live(), afterStateUpdated(), validation rules, etc.

Panel at a glance

ViewportBehavior
Desktop (≥768px)Floating glass panel near the trigger; draggable header; backdrop dismiss
Mobile (<768px)Bottom sheet with drag handle, larger keys, safe-area padding
FeatureDetail
Singleton panelOne teleported panel serves every CalculatorField on the page
Per-field sessionsExpression, result preview, and label are remembered per field id
Context switchOpen panel on field A, click field B’s trigger — panel stays open and restores B’s session
Display vs InsertPanel shows full precision while typing; field constraints apply only on Insert
The panel mount renders once per page via @once in the field Blade view (data-fff-calculator-panel-host).

Basic usage

Standard weight input

use Bjanczak\FilamentFlexFields\Filament\Forms\Components\CalculatorField;

CalculatorField::make('weight')
    ->label('Cargo weight (kg)')
    ->decimalPlaces(2)
    ->step(0.01)
    ->minValue(0)
    ->maxValue(99999)
    ->required();

Filament resource form

use Filament\Forms\Form;
use Bjanczak\FilamentFlexFields\Filament\Forms\Components\CalculatorField;

public static function form(Form $form): Form
{
    return $form->schema([
        CalculatorField::make('weight')
            ->label('Cargo weight (kg)')
            ->decimalPlaces(2)
            ->maxLength(8)
            ->helperText('Use the calculator icon for compound math.')
            ->columnSpanFull()
            ->required(),

        CalculatorField::make('quantity')
            ->label('Container count')
            ->integer()
            ->minValue(0)
            ->maxValue(9999),
    ]);
}

Integer-only quantity

CalculatorField::make('quantity')
    ->label('Units')
    ->integer()
    ->minValue(0)
    ->maxValue(9999);

State & validation

Stored value

State is a numeric value (int or float) or null when nullable and empty.
$record->weight;   // float|null — e.g. 1250.5
$record->quantity; // int|null   — e.g. 48
Users can enter values by typing in the input or by Insert from the calculator panel. Both paths run the same normalization pipeline.

Validation rules (built-in)

RuleWhen
integerWhen ->integer()
min:$nWhen ->minValue($n) and value is filled
max:$nWhen ->maxValue($n)
max_digits:$nWhen ->maxLength($n) — counts digits only (ignores ., -)
requiredWhen ->required()
CalculatorField::make('margin')
    ->decimalPlaces(2)
    ->minValue(0)
    ->maxValue(100)
    ->required();

Direct typing vs calculator Insert

StageWhat happens
Panel displayFull-precision preview; decimalPlaces() does not round the live result
Insert / blur / inputnormalizeNumericFieldValue() applies decimalPlaces, roundingMode, min/max, step, integer
Example: with ->decimalPlaces(2) and ->roundingMode('truncate'), typing 9.9999 in the panel shows 9.9999; Insert stores 9.99.

Numeric constraints

All methods accept Closure unless noted.
MethodTypeDefaultDescription
minValue(scalar|Closure|null $value)SetupnullLower bound; adds min: validation rule
maxValue(scalar|Closure|null $value)SetupnullUpper bound; adds max: validation rule
step(int|float|Closure $step)Setup1Snap inserted/typed values to nearest step
integer(bool|Closure $condition = true)SetupfalseRestrict to whole numbers; adds integer rule
decimalPlaces(int|Closure|null $places)SetupnullFixed decimal precision on normalize
maxLength(int|Closure|null $length)SetupnullMax digit count (ignores non-digits)
roundingMode(string|Closure $mode)Setup'truncate'round, ceil, floor, or truncate

Rounding modes

CalculatorField::make('price')
    ->decimalPlaces(2)
    ->roundingMode('truncate'); // default — toward zero

CalculatorField::make('price')->roundingMode('round');  // half-up
CalculatorField::make('price')->roundingMode('ceil');  // toward +∞
CalculatorField::make('price')->roundingMode('floor'); // toward −∞

Max digit length

Useful for database columns with fixed precision:
CalculatorField::make('weight')
    ->maxLength(8)      // e.g. 999999.99 → 8 digits
    ->decimalPlaces(2);

Visual options

Built on FlexTextInput styling — same variant and size tokens.

Variants

CalculatorField::make('weight')->variant('primary');   // default
CalculatorField::make('weight')->variant('secondary');
CalculatorField::make('weight')->variant('flat');
CalculatorField::make('weight')->variant('soft');

Sizes

CalculatorField::make('weight')->size('sm'); // default: md
CalculatorField::make('weight')->size('lg');
See Control size (sm, md, lg).

Rounding (border radius)

CalculatorField::make('weight')
    ->rounding('full'); // 'default', 'native', 'md', 'lg', 'xl', 'full'
Per-field rounding() overrides the global default from config/filament-flex-fields.php (ui.field_rounding).

Placeholder & calculator icon

use Bjanczak\FilamentFlexFields\Support\GravityIcon;

CalculatorField::make('weight')
    ->placeholder('0')
    ->calculatorIcon(GravityIcon::make('calculator'));
Default icon: GravityIcon::make('calculator').

Read-only & disabled

CalculatorField::make('weight')->readOnly();  // input locked; panel still usable
CalculatorField::make('weight')->disabled();  // input and trigger locked

Calculator keypad semantics

iOS-style layout: digits, AC, ±, %, +, , ×, ÷, decimal, equals, backspace.

Operators & precedence

Standard infix evaluation with +, , ×, ÷, parentheses, and unary minus. Multiplication and division bind tighter than addition and subtraction.

Percent (%)

Divides the last operand by 100 (iOS behavior):
Expression before %After %
90.09
9+6-99+6-0.09

Sign toggle (±)

Negates the last operand. After an operator, negatives use parentheses:
ExpressionAfter ±
12+512+(-5)
12+(-5)12+5

All clear (AC)

Clears the expression and resets the display to 0. Insert after AC writes 0 into the field (subject to normalization).

Equals (=)

Replaces the expression with the computed result so you can continue calculating.

Insert

Writes the primary display value into the active field, normalizes it, syncs Livewire state, and keeps the panel open. The field trigger shows is-panel-target while its session is active.

Complete configuration API

MethodTypeDefaultDescription
variant(string|Closure $variant)Setup'primary'primary, secondary, flat, soft
size(string|ControlSize|Closure $size)Setup'md'sm, md, lg
rounding(string|Closure|null $rounding)SetupconfigBorder radius token
placeholder(string|Closure|null $placeholder)Setup'0' (translated)Empty input hint
calculatorIcon(string|BackedEnum|Htmlable|Closure|null $icon)SetupGravity calculatorTrigger button icon
focusOutline(bool|Closure $condition = true)SetupfalseShow focus ring on input
minValue, maxValue, step, integer, decimalPlaces, maxLength, roundingModeSetupsee aboveNumeric constraints
required(), disabled(), readOnly()SetupStandard Filament field APIs

Public helper methods

MethodReturnsDescription
getVariant()stringResolved visual variant
getSize()stringResolved size token
getRounding()stringResolved rounding token
getMinValue() / getMaxValue()scalar|nullBounds
getStep()int|floatStep increment
isInteger()boolWhole-number mode
getDecimalPlaces()?intDecimal precision
getMaxLength()?intMax digit count
getRoundingMode()stringActive rounding mode
getCalculatorIcon()string|BackedEnum|HtmlableTrigger icon
getCalculatorFieldId()stringSession key (state path or field name)
getWrapperClasses()list<string>CSS class list for the root
shouldShowFocusOutline()boolFocus ring visibility

Real-world examples

Shipping form with live total

CalculatorField::make('unit_weight')
    ->label('Unit weight (kg)')
    ->decimalPlaces(2)
    ->live()
    ->afterStateUpdated(fn (Set $set, ?float $state) => $set(
        'total_weight',
        ($state ?? 0) * (int) ($get('quantity') ?? 0),
    )),

CalculatorField::make('quantity')
    ->label('Quantity')
    ->integer()
    ->minValue(1)
    ->live(),

Margin with percent-friendly calculator

CalculatorField::make('margin')
    ->label('Margin (%)')
    ->decimalPlaces(2)
    ->minValue(0)
    ->maxValue(100)
    ->helperText('Type 15 in the panel, press % → 0.15, or enter 15 directly.');

Wizard step — optional estimate

Wizard\Step::make('Cargo')->schema([
    CalculatorField::make('estimated_weight')
        ->label('Estimated weight (optional)')
        ->decimalPlaces(1)
        ->minValue(0)
        ->columnSpanFull(),
]),

Strict integer inventory

CalculatorField::make('stock')
    ->integer()
    ->minValue(0)
    ->maxValue(99999)
    ->maxLength(5)
    ->roundingMode('floor');

Database & Eloquent

Migration

Schema::create('shipments', function (Blueprint $table) {
    $table->id();
    $table->decimal('weight', 10, 2)->nullable();
    $table->unsignedInteger('quantity')->nullable();
    $table->decimal('margin', 5, 2)->nullable();
    $table->timestamps();
});

Model

class Shipment extends Model
{
    protected $fillable = ['weight', 'quantity', 'margin'];

    protected function casts(): array
    {
        return [
            'weight' => 'decimal:2',
            'quantity' => 'integer',
            'margin' => 'decimal:2',
        ];
    }
}

Assets & deployment

Asset typeBundlesHow
CSScalculator-field, calculator-panel (depends on flex-text-input)Lazy-loaded when field renders
Alpine JScalculator-field.js, panel behaviorFilament x-load on demand
Panel mountcalculator-panel-mount.blade.phpRendered once per page
Run after install or upgrade:
php artisan filament:assets
Stylesheet dependency graph: calculator-fieldflex-text-input, calculator-panel.

Accessibility

  • Field root uses role="group" with aria-label from the field label.
  • Calculator trigger exposes aria-label (translatable Open calculator) and aria-expanded when active.
  • Panel close button has an accessible label; keypad keys are native <button> elements.
  • Mobile bottom sheet includes a visual drag handle (aria-hidden).
  • Respects prefers-reduced-motion for panel and context-switch animations.
Translations live under filament-flex-fields::default.calculator.* (title, placeholder, open, apply, close).

Performance

MechanismWhat it does
Lazy CSSLoads calculator bundles only when a field renders
Lazy AlpineField and panel scripts load via Filament x-load
Singleton panelOne DOM portal regardless of field count
Session mapIn-memory Map per field id — no server round-trips while calculating

Playground

/admin/flex-fields-playground/calculator-field See Playground for setup.
ComponentWhen to use instead
NumberStepperSimple ± stepping without free-form math
CurrencyFieldLocale-aware money input with minor-unit storage
FlexTextInputPlain numeric text without a calculator
FlexSliderVisual range selection

CSS classes (reference)

Field

ClassRole
fff-calculator-fieldRoot wrapper
fff-calculator-field--{sm|md|lg}Size variant
fff-calculator-field--{primary|secondary|flat|soft}Visual variant
fff-calculator-field__shellInput shell (shared with FlexTextInput)
fff-calculator-field__rowInput + trigger row
fff-calculator-field__controlInput control area
fff-calculator-field__input-wrapInput wrapper
fff-calculator-field__inputNative text input
fff-calculator-field__triggerCalculator open button
fff-calculator-field__trigger-iconTrigger icon
is-panel-targetActive field while panel is open for this session
is-disabled / is-read-onlyLocked states
Field also reuses fff-flex-text-input and fff-flex-text-input--{size\|variant} classes.

Panel (teleported)

ClassRole
fff-calculator-panel-portalTeleport wrapper
fff-calculator-panel__backdropClick-outside dismiss layer
fff-calculator-panelPanel root
fff-calculator-panel.is-mobileBottom-sheet layout
fff-calculator-panel.is-open / .is-closingAnimation states
fff-calculator-panel.is-darkDark color scheme
fff-calculator-panel__headerDraggable header (desktop)
fff-calculator-panel__dragMobile drag handle
fff-calculator-panel__displayExpression + result area
fff-calculator-panel__expressionCurrent expression line
fff-calculator-panel__resultLive result / preview
fff-calculator-panel__keypadKey grid
fff-calculator-panel__keyIndividual key
fff-calculator-panel__key.is-digitDigit keys
fff-calculator-panel__key.is-functionAC, ±, %
fff-calculator-panel__key.is-operator+, , ×, ÷, =
fff-calculator-panel__btn--primaryInsert button
data-fff-calculator-panel-hostSingleton mount marker (once per page)